【Linux网络编程学习】使用socket实现简单服务器——多进程多线程版本

此为牛客Linux C++课程和黑马Linux系统编程笔记。

1. 多进程版

1.1 思路

大体思路与上一篇的单进程版服务器–客户端类似,都是遵循下图:
在这里插入图片描述
多进程版本有以下几点需要注意:

  1. 由于TCP是点对点连接,服务器主进程连接了一个客户端以后就无法再与其他客户端相连,所以多进程版的服务器中的父进程只负责监听,连接并信息传输的工作交给子进程完成。每当accept到一个客户端的连接请求,就fork出一个子进程来处理。
  2. 父进程负责监听的同时,也要回收子进程的资源,避免产生僵尸进程。

1.2 服务端

/*实现一个简单的多进程服务器-客户端通信*/#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>// 设定一个服务器端口号
#define SERV_IP "127.0.0.1"
#define SERV_PORT 7777void wait_child(int signo) {while(1) {waitpid(-1, NULL, WNOHANG); // 回收任意子进程并设置成非阻塞}return;
}int main()
{int lfd, cfd; // 用于监听的文件描述符和用于与客户端通信的文件描述符struct sockaddr_in serv_addr, clie_addr; // 服务器和客户端的sockaddrlfd = socket(AF_INET, SOCK_STREAM, 0); // 服务端套接字serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT); // 注意转化成网络字节序inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); // 注意转化成网络字节序bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); // 与ip和端口号绑定listen(lfd, 128); // 指定最多同时连接数128int pid;while(1) {// 父进程循环进行acceptint clie_addr_len = sizeof(clie_addr);cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len);char clie_IP[BUFSIZ];printf("Client IP: %s, client port: %d connected\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),ntohs(clie_addr.sin_port));pid = fork();if(pid > 0) {// 父进程close(cfd); // 父进程只需要循环监听,不需要与客户端进行数据交互,故关闭signal(SIGCHLD, wait_child); // 回收子进程,避免产生僵尸进程,也可以用sigaction} else if(pid == 0) {// 子进程,注意这里的写法,因为子进程不需要循环,所以把子进程的逻辑定义在循环外部,在这里break出去close(lfd); // 子进程不需要监听,所以把子进程的lfd关掉break;} else {perror("fork error");exit(1);}}if(pid == 0) {// 子进程负责跟一个客户端完成数据交互while(1) {char buf[BUFSIZ];int len;len = read(cfd, buf, sizeof(buf));if(len > 0) {// 小写转大写int i;for(i = 0; i < len; ++i) {if(buf[i] >= 'a' && buf[i] <= 'z') {buf[i] -= 32;}}write(cfd, buf, len); // 写回给客户端write(STDOUT_FILENO, buf, len);} else if(len == 0){// ret为0说明读完了,表示客户端已关闭close(cfd);exit(1);} else {perror("read error");exit(1);}}}return 0;
}

为突出主体,未写错误检测与错误提示

1.3 客户端

/*实现一个简单的多进程服务器-客户端通信*/#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>// 服务器的ip和端口
#define SERV_IP "127.0.0.1"
#define SERV_PORT 7777int main()
{int ret; // 用于错误检测int cfd; // 用于写入数据传输给服务端的socket的文件描述符cfd = socket(AF_INET, SOCK_STREAM, 0);if(cfd == -1) {perror("socket error");exit(1);}// bind()  可以不调用bind(), linux会隐式地绑定struct sockaddr_in serv_addr; // 因为要连接服务端,这里的sockadd_in是用于指定服务端的ip和端口bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); // 调用ip转换函数,把字符串ip转化为网络字节序ret = connect(cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));if(ret == -1) {perror("connect error");exit(1);}// 从终端读取内容while(1) {char buf[BUFSIZ];fgets(buf, sizeof(buf), stdin);  // 读一行// 写入到cfd中,传输给服务端ret = write(cfd, buf, strlen(buf)); // 注意不要写成sizeof(buf),sizeof是在内存中所占的大小,strlen是到第一个'\0'位止。if(ret == -1) {perror("write error");exit(1);}// read在读socket时默认时阻塞的,阻塞等待服务端传输数据int len;len = read(cfd, buf, sizeof(buf));if(len == -1) {perror("read error");exit(1);}printf("%s", buf);}close(cfd);return 0;
}

为突出主体,未写错误检测与错误提示

2. 多线程版本

使用与多进程完全类似的思路,无非是用线程来实现。

2.1 服务端

/*实现一个简单的多线程服务器-客户端通信*/#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <pthread.h>
#include <strings.h>// 设定一个服务器端口号
#define SERV_IP "127.0.0.1"
#define SERV_PORT 8888struct s_info /* 该结构体用于给子线程函数传参 */
{struct sockaddr_in clie_addr; // 客户端ip和端口号int cfd;                      // 通信所用的文件描述符
};void* do_work(void* arg) {// 子线程负责小写转大写struct s_info *ts = (struct s_info*)arg;while(1) {char buf[BUFSIZ];int len;len = read(ts->cfd, buf, sizeof(buf));if(len > 0) {// 小写转大写int i;for(i = 0; i < len; ++i) {if(buf[i] >= 'a' && buf[i] <= 'z') {buf[i] -= 32;}}write(ts->cfd, buf, len); // 写回给客户端write(STDOUT_FILENO, buf, len);} else if(len == 0){// ret为0说明读完了,表示客户端已关闭close(ts->cfd);exit(1);} else {perror("read error");exit(1);}}return (void*)0;
}int main()
{int lfd, cfd; // 用于监听的文件描述符和用于与客户端通信的文件描述符struct sockaddr_in serv_addr, clie_addr; // 服务器和客户端的sockaddrlfd = socket(AF_INET, SOCK_STREAM, 0); // 服务端套接字serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT); // 注意转化成网络字节序inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); // 注意转化成网络字节序bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); // 与ip和端口号绑定listen(lfd, 128); // 指定最多同时连接数128struct s_info ts;pthread_t tid;while(1) {// 主线程循环进行acceptint clie_addr_len = sizeof(clie_addr);cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len);char clie_IP[BUFSIZ];printf("Client IP: %s, client port: %d connected\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),ntohs(clie_addr.sin_port));bzero(&ts, sizeof(ts));ts.cfd = cfd;ts.clie_addr = clie_addr;pthread_create(&tid, NULL, do_work, (void*)&ts);pthread_detach(tid);          // 子线程分离,防止产生僵尸线程}printf("what happened?");return 0;
}

2.2 客户端

/*实现一个简单的多线程服务器-客户端通信*/#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>// 服务器的ip和端口
#define SERV_IP "127.0.0.1"
#define SERV_PORT 8888int main()
{int ret; // 用于错误检测int cfd; // 用于写入数据传输给服务端的socket的文件描述符cfd = socket(AF_INET, SOCK_STREAM, 0);if(cfd == -1) {perror("socket error");exit(1);}// bind()  可以不调用bind(), linux会隐式地绑定struct sockaddr_in serv_addr; // 因为要连接服务端,这里的sockadd_in是用于指定服务端的ip和端口bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); // 调用ip转换函数,把字符串ip转化为网络字节序ret = connect(cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));if(ret == -1) {perror("connect error");exit(1);}// 从终端读取内容while(1) {char buf[BUFSIZ];fgets(buf, sizeof(buf), stdin);  // 读一行// 写入到cfd中,传输给服务端ret = write(cfd, buf, strlen(buf)); // 注意不要写成sizeof(buf),sizeof是在内存中所占的大小,strlen是到第一个'\0'位止。if(ret == -1) {perror("write error");exit(1);}// read在读socket时默认时阻塞的,阻塞等待服务端传输数据int len;len = read(cfd, buf, sizeof(buf));if(len == -1) {perror("read error");exit(1);}printf("%s", buf);}close(cfd);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384556.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux网络编程学习】I/O多路复用——select和poll

此为牛客Linux C课程和黑马Linux系统编程笔记。 0. I/O多路复用 所谓I/O就是对socket提供的内存缓冲区的写入和读出。 多路复用就是指程序能同时监听多个文件描述符。 之前的学习中写了多进程和多线程版的简单服务器模型&#xff0c;但是有个问题&#xff1a;每次新来一个客…

【Linux网络编程学习】阻塞、非阻塞、同步、异步以及五种I/O模型

文章目录1. 基本概念1.1 阻塞与非阻塞1.2 同步与异步1.3 为什么没有“异步阻塞”2. 五种IO模型2.1 阻塞 blocking2.2 非阻塞 non-blocking2.3. IO复用&#xff08;IO multiplexing&#xff09;2.4 信号驱动&#xff08;signal-driven&#xff09;2.5 异步&#xff08;asynchron…

STM32时钟树解析

本人之前其实也用STM32做过一些小东西&#xff0c;但因为时钟的初始化一般是直接在SystemInit时钟系统初始化函数里直接配置为72MHz&#xff0c;所以对于STM32的时钟框图并没有怎么理会&#xff0c;今天刚好有空就重新看了一下并写一篇博客记录一下吧&#xff0c;以免以后又忘了…

S3C2440时钟体系

S3C2440在默认情况下&#xff0c;整个系统全靠一个12MHz的外部晶振提供频率来工作运行的&#xff0c;也就是说CPU、内存、UART、ADC等所有需要用到时钟频率的硬件都工作在12MHz下&#xff0c;但是通过查阅芯片手册我们知道CPU时钟最高可为400MHZ&#xff0c;那么怎么设置时钟让…

关于MCU、CPU扩展SDRAM的一个小知识

像上图这种硬件电路图上的16个数据位和我们在初始化SDRAM的时候设置的16位数据位宽是指我们读写SDRAM的时候可以同时读写16个数据位&#xff0c;数据线越多肯定越快&#xff0c;但是数据线也不可能无限增加&#xff0c;我们在程序里是可以读写8位&#xff0c;16位&#xff0c;3…

S3C2440扩展SDRAM

本文主要目的是记录一下S3C2440扩展SDRAM的一些知识&#xff0c;方便以后查阅。 通过查阅手册我们知道&#xff0c;2440有8个可以用来扩展内存的BANK&#xff0c;其中第6和第7还可用来扩展SDRAM 下面我们来看一下2440扩展SDRAM需要设置哪些寄存器。 一、BWSCON寄存器 该寄存器…

汇编语言的相对跳转和绝对跳转以及反汇编代码解析

上图第一行的b1 main为相对跳转&#xff0c;即跳转到pcoffset,其中pc为当前pc值&#xff0c;offset可以理解为偏移地址&#xff0c;也就是根据当前所在地址加上偏移地址实现跳转&#xff0c;为相对跳转。 我们来看看它的反汇编代码 上图清除完bss区后使用b1指令跳转到30000668…

韦东山嵌入式第一期14课第004节_und异常模示程序示例_P笔记

本节课的第一个程序韦老师是想让大家见识一下未定义异常&#xff0c;而第二个程序是对第一个程序进行改进&#xff0c;防止在某些条件下执行不了&#xff0c;下面就来讲一下第2个程序改进了哪些地方并且有什么用。 程序在此路径中&#xff1a;源码文档图片\源码\源码_20180321…

关于NOR FLASH地址左右移的问题

问题引入&#xff1a;不知道你会不会有这样的疑问&#xff1a;为什么在发送解锁命令时&#xff0c;我们不用右移一位&#xff0c;而发送扇区地址时却要右移一位&#xff08;nor_cmd函数内部已经左移一位&#xff09;&#xff0c;这里先补充说明一下说明是cpu角度和nor角度&…

在linux下利用ls命令进行模糊查找

如上图&#xff0c;我们当前路径下有三个文件&#xff0c;分别为helloworld.c以及helloworld和1.c&#xff0c;直接输入命令ls则显示所有文件&#xff0c;我们可以利用ls 加*的方向进行模糊查找。 输入ls 目录名 形式的命令行&#xff0c;则是对该目录名下的文件全部进行显示&a…

Linux下没有包含头文件(不知是哪个)导致编译无法通过的解决心得

最近写程序的时候编译出错了&#xff0c;提示信息为&#xff1a;invalid use of undefined type fb_var_screeninfo。显示根据英文知道是没有定义 fb_var_screeninfo这个类型&#xff0c;明显是缺少了某个头文件&#xff0c;但是缺少哪个头文件以及有什么又快又好的解决方法呢&…

Linux编译程序时加-I指定头文件位置

Linux下编译出现以下错误&#xff0c;错误的原因是在/usr/local/arm/arm-2009q3/bin/../arm-none-linux-gnueabi/libc/usr/include/freetype/config/下找不到ftheader.h&#xff0c;而我到该目录下看&#xff0c;发现路径是这样的rootubuntu:/usr/local/arm/arm-2009q3/arm-non…

关于源文件用不同的编码方式编写,会导致执行结果不一样的现象及解决方法

如果我们编写以下程序&#xff0c;并分别另存为ANSI和UTF-8两种不同的编码方式保存&#xff0c;放到Linux下编译并运行如下图&#xff0c;两端相同的程序以不同的编码方式保存编译后的运行结果不一样&#xff0c;./ansi采用ANSI编码方式&#xff0c;会自动采用GBK方式来保存中文…

arm-linux-gcc静态编译和动态编译的区别

很多教程会提到加上-static是静态编译&#xff0c;但对于新手来说没有用例子来说明可能不太好理解&#xff0c;今天我就介绍一下关于这方面知识的一个例子&#xff1a; 最近在做一个关于freetype字体的东西&#xff0c;需要依赖freetype官方提供的库&#xff0c;我已经把电脑这…

从0到1写RT-Thread内核——线程定义及切换的实现

从0写RT-Thread内核之线程定义及切换的实现具体可以分为以下六步来实现 一&#xff1a;分别定义线程栈、线程函数、线程控制块&#xff1b; ALIGN(RT_ALIGN_SIZE)//设置4字节对齐 /* 定义线程栈 */ rt_uint8_t rt_flag1_thread_stack[512]; rt_uint8_t rt_flag2_thread_stack…

从0到1写RT-Thread内核——临界段的保护

临界段就是一段在执行的时候不能被中断的代码段&#xff0c;在RT-Thread里&#xff0c;临界段最常出现的就是对全局变量的操作&#xff08;类似Linux下的锁&#xff09;。RT-Thread对临界段的保护是直接把中断全部关了&#xff0c;NMI FAULT和硬FAULT除外。下图是3个关于中断屏…

从0到1写RT-Thread内核——空闲线程与阻塞延时的实现

在之前写的另外一篇文章——<从0到1写RT-Thread内核——线程定义及切换的实现>中线程体内的延时使用的是软件延时&#xff0c;即还是让CPU空等来达到延时的效果。RTOS中的延时叫阻塞延时&#xff0c;即线程需要延时的时候&#xff0c;线程会放弃CPU的使用权&#xff0c;C…

从0到1写RT-Thread内核——支持多优先级

在本章之前&#xff0c;RT-Thread还没有支持多优先级&#xff0c;我们手动指定了第一个运行的线程&#xff0c;并在此之后三个线程&#xff08;包括空闲线程&#xff09;互相切换&#xff0c;在本章中我们加入优先级的功能&#xff0c;第一个运行的程序是就绪列表里优先级最高的…

AD软件之模块化原理图

首先我们创建两个原理图文件 然后我们在Sheet2.SchDoc里放置一个页面符并双击绿色的方框 选择目标文件 我们选择我们刚才创建的Sheet4.SchDoc 然后在 视图——>面板——>Navigator选项 里点一下交互式导航 就可以看到Sheet4.SchDoc被添加到Sheet2.SchDoc下面了 通过上面…

AD软件操作技巧

本文介绍一些关于AD软件的实用小操作&#xff0c;这些小技巧可以大大的减少我们的工作量 一.批量操作丝印&#xff08;或者操作别的东西也可以&#xff0c;主要是凸显批量操作的思想&#xff09; 如下图假设我们工程里有很多丝印和焊盘等等&#xff0c;现在我想改批量地修改丝…