【Linux网络编程】01:Socket多进程
创始人
2024-06-03 22:53:01
0

Socket多进程


OVERVIEW

  • Socket多进程
        • 1.Server
        • 2.Client
        • 3.bug&rethink
        • 4.source code

调用socket、connect、bind、listen、accept、send、recv

在这里插入图片描述

1.Server

//1.server.c
#include "head.h"
#include "common.h"#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)int main(int argc, char *argv[]) {//./a.out -p port//1.命令行解析if (argc != 3) {fprintf(stderr, "Usage : %s -p port", argv[0]);exit(1);}int opt;int port;int sockfd;while ((opt = getopt(argc, argv, "p:")) != -1) {switch (opt) {case 'p':port = atoi(optarg);break;default:fprintf(stderr, "Usage : %s -p port\n", argv[0]);exit(1);}}//2.创建socketif ((sockfd = socketCreate(port)) < 0) handle_error("socketCreate");//3.accept循环的接受客户端对server的连接while (1) {int newfd;if ((newfd = accept(sockfd, NULL, NULL)) < 0) handle_error("accept");//不关注客户端的ip地址与端口号NULLprintf(" : accept a client!\n");//4.recv接受到消息while (1) {char buff[1024] = {0};ssize_t rsize = recv(newfd, buff, sizeof(buff), 0);if (rsize > 0) printf(" : %s\n", buff);}}return 0;
}
//common.h
#ifndef _COMMON_H
#define _COMMON_Hint socketCreate(int port);#endif
//common.c
#include "head.h"int socketCreate(int port) {//1.创建套接字int sockfd;struct sockaddr_in addr;if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return -1;addr.sin_family = AF_INET;addr.sin_port = htons(port);//主机字节序转换为网络字节序addr.sin_addr.s_addr = inet_addr("0.0.0.0");//将网络字节序转化为主机字节序 0.0.0.0表示不关注消息传来的地址//2.bind绑定套接字与结构体信息 listenif (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0) return -1;if (listen(sockfd, 20) < 0) return -1;return sockfd;
} 

将2000端口作为服务端绑定的端口,启动服务端程序

在这里插入图片描述

新建一个窗口利用netstat -alnt命令查看已经在使用的端口及其当前状态:LISTEN

在这里插入图片描述

利用telnet ip port命令对服务端进行测试(需要开放阿里云服务器安全组,并设置linux系统的防火墙才能进行测试):

在这里插入图片描述

服务端接收到消息并receive打印输出消息:

在这里插入图片描述

可以看到服务端成功接收到消息,并打印了消息内容。

2.Client

#include "head.h"#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)int main(int argc, char *argv[]) {//./a.out ip portif (argc != 3) {fprintf(stderr, "Usage : %s ip port\n", argv[0]);exit(1);}//1.客户端打开一个socketint sockfd;if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) handle_error("socket");//2.定义结构体用于绑定端口号、ip地址(存放服务端的具体信息)struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(atoi(argv[2]));//端口号server.sin_addr.s_addr = inet_addr(argv[1]);//ip地址//3.建立连接connectionif (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) handle_error("connect");//4.send发送数据while (1) {//循环发送消息char buff[1024] = {0};scanf("%[^\n]s", buff);//输入可含空格的字符串getchar();//吞掉一个换行//只要向文件描述符中写入 tcp服务就会帮助发送消息//With a zero flags argument, send is equivalent to write(2).if (strlen(buff)) send(sockfd, buff, sizeof(buff), 0);}return 0;
}

同时启动客户端与服务端,从客户端发送消息,可以看到服务端能够正常接收消息并打印:

在这里插入图片描述

3.bug&rethink

程序还有几个功能上没有处理的bug:

  1. 客户端ctrl+C结束之后,服务端无法接收到客户端退出连接的状态。
  2. 如果创建多个客户端尝试连接服务端,服务端将无法接收到客户端的连接请求。
  3. 服务端能够知道是谁向我发送的数据(打印出客户端的ip地址和端口号)

问题1解决:在client.c中利用signal信号,并添加一个closeSock函数对客户端ctrl+c后向服务端做出相应:

#include "head.h"#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)int sockfd;//ctrl+c信号处理
void closeSock(int signum) {send(sockfd, " : I am leaving...", 27, 0);close(sockfd);exit(1);
}int main(int argc, char *argv[]) {//./a.out ip portif (argc != 3) {fprintf(stderr, "Usage : %s ip port\n", argv[0]);exit(1);}//1.客户端打开一个socketif ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) handle_error("socket");//2.定义结构体用于绑定端口号、ip地址(存放服务端的具体信息)struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(atoi(argv[2]));//端口号server.sin_addr.s_addr = inet_addr(argv[1]);//ip地址signal(SIGINT, closeSock);//3.建立连接connectionif (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) handle_error("connect");printf("connect sccuess!\n");//4.send发送数据while (1) {//循环发送消息char buff[1024] = {0};scanf("%[^\n]s", buff);//输入可含空格的字符串getchar();//吞掉一个换行//只要向文件描述符中写入 tcp服务就会帮助发送消息//With a zero flags argument, send is equivalent to write(2).if (strlen(buff)) send(sockfd, buff, sizeof(buff), 0);}return 0;
}

问题2解决:在server.c中fork多个进程,父进程用于accept、子进程用于receive并将接受到的信息进行打印。

#include "head.h"
#include "common.h"#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)int main(int argc, char *argv[]) {//./a.out -p port//1.命令行解析if (argc != 3) {fprintf(stderr, "Usage : %s -p port", argv[0]);exit(1);}int opt;int port;int sockfd;while ((opt = getopt(argc, argv, "p:")) != -1) {switch (opt) {case 'p':port = atoi(optarg);break;default:fprintf(stderr, "Usage : %s -p port\n", argv[0]);exit(1);}}//2.创建socketif ((sockfd = socketCreate(port)) < 0) handle_error("socketCreate");//3.accept循环的接受客户端对server的连接while (1) {int pid;int newfd;//新建的文件描述符if ((newfd = accept(sockfd, NULL, NULL)) < 0) handle_error("accept");//不关注客户端的ip地址与端口号NULLprintf(" : accept a client!\n");//4.子进程recv接受到消息if ((pid = fork()) < 0) handle_error("fork");if (pid == 0) {//子进程进行操作while (1) {char buff[1024] = {0};ssize_t rsize = recv(newfd, buff, sizeof(buff), 0);if (rsize > 0) {printf(" : %s\n", buff);} else {close(sockfd);break;}}printf(" : a client left!\n");exit(0);}}return 0;
}

在这里插入图片描述

问题3解决:在server.c中对accept函数位置进行修改,获取到client端的具体信息显示并打印在聊天的过程中:

#include "head.h"
#include "common.h"#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)int main(int argc, char *argv[]) {//./a.out -p port//1.命令行解析if (argc != 3) {fprintf(stderr, "Usage : %s -p port", argv[0]);exit(1);}int opt;int port;int sockfd;while ((opt = getopt(argc, argv, "p:")) != -1) {switch (opt) {case 'p':port = atoi(optarg);break;default:fprintf(stderr, "Usage : %s -p port\n", argv[0]);exit(1);}}//2.创建socketif ((sockfd = socketCreate(port)) < 0) handle_error("socketCreate");//3.accept循环的接受客户端对server的连接while (1) {int pid;int newfd;//新建的文件描述符struct sockaddr_in client;//用于存放临时建立连接的客户端的信息socklen_t len = sizeof(client);if ((newfd = accept(sockfd, (struct sockaddr *)&client, &len)) < 0) handle_error("accept");//不关注客户端的ip地址与端口号NULLprintf(" %s:%d: accept a client!\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));//4.子进程recv接受到消息if ((pid = fork()) < 0) handle_error("fork");if (pid == 0) {//子进程进行操作while (1) {char buff[1024] = {0};ssize_t rsize = recv(newfd, buff, sizeof(buff), 0);//三次握手四次挥手if (rsize > 0) {printf(" %s:%d: %s\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), buff);} else {close(sockfd);break;}}printf(" : %s:%d left!\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));exit(0);}}return 0;
}

在这里插入图片描述

在这里插入图片描述

遗留bug:在多个客户端与服务端建立连接后,服务端先于客户端关闭,而客户端未收到通知则会产生CLOSE_WAIT与FIN_WAIT2端口状态(导致端口重用出现报错问题,端口无效占用,系统端口很快将被用尽)。

在这里插入图片描述

4.source code

//common.h
#ifndef _COMMON_H
#define _COMMON_Hint socketCreate(int port);
int socketConnect(const char *ip, int port);#endif
//common.c
#include "head.h"int socketCreate(int port) {//1.创建套接字int sockfd;struct sockaddr_in addr;if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return -1;addr.sin_family = AF_INET;addr.sin_port = htons(port);//主机字节序转换为网络字节序addr.sin_addr.s_addr = inet_addr("0.0.0.0");//将网络字节序转化为主机字节序 0.0.0.0表示不关注消息传来的地址//2.bind绑定套接字与结构体信息 listenif (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0) return -1;if (listen(sockfd, 20) < 0) return -1;return sockfd;
}int socketConnect(const char *ip, int port) {int sockfd;//1.客户端打开一个socketif ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return -1;//2.定义结构体用于绑定端口号、ip地址(存放服务端的具体信息)struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(port);//端口号server.sin_addr.s_addr = inet_addr(ip);//ip地址//3.建立连接connectionif (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) return -1;return sockfd;
}
//server.c
#include "head.h"
#include "common.h"#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)int main(int argc, char *argv[]) {//./a.out -p port//1.命令行解析if (argc != 3) {fprintf(stderr, "Usage : %s -p port", argv[0]);exit(1);}int opt;int port;int sockfd;while ((opt = getopt(argc, argv, "p:")) != -1) {switch (opt) {case 'p':port = atoi(optarg);break;default:fprintf(stderr, "Usage : %s -p port\n", argv[0]);exit(1);}}//2.创建socketif ((sockfd = socketCreate(port)) < 0) handle_error("socketCreate");//3.accept循环的接受客户端对server的连接while (1) {int pid;int newfd;//新建的文件描述符struct sockaddr_in client;//用于存放临时建立连接的客户端的信息socklen_t len = sizeof(client);if ((newfd = accept(sockfd, (struct sockaddr *)&client, &len)) < 0) handle_error("accept");//不关注客户端的ip地址与端口号NULLprintf(" %s:%d: accept a client!\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));//4.子进程recv接受到消息if ((pid = fork()) < 0) handle_error("fork");if (pid == 0) {//子进程进行操作while (1) {char buff[1024] = {0};ssize_t rsize = recv(newfd, buff, sizeof(buff), 0);//三次握手四次挥手if (rsize > 0) {printf(" %s:%d: %s\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), buff);} else {close(sockfd);break;}}printf(" : %s:%d has left!\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));exit(0);}}return 0;
}
//client.c
#include "head.h"
#include "common.h"#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)int sockfd;//ctrl+c信号处理
void closeSock(int signum) {send(sockfd, "I am leaving...", 27, 0);close(sockfd);exit(0);
}int main(int argc, char *argv[]) {//./a.out ip portif (argc != 3) {fprintf(stderr, "Usage : %s ip port\n", argv[0]);exit(1);}signal(SIGINT, closeSock);//1.建立连接connectif ((sockfd = socketConnect(argv[1], atoi(argv[2]))) < 0) handle_error("socketConnect");printf("connect sccuess!\n");//2.send发送数据while (1) {//循环发送消息char buff[1024] = {0};scanf("%[^\n]s", buff);//输入可含空格的字符串getchar();//吞掉一个换行//只要向文件描述符中写入 tcp服务就会帮助发送消息//With a zero flags argument, send is equivalent to write(2).if (strlen(buff)) send(sockfd, buff, sizeof(buff), 0);}return 0;
}

相关内容

热门资讯

122.(leaflet篇)l... 听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
Vue使用pdf-lib为文件... 之前也写过两篇预览pdf的,但是没有加水印,这是链接:Vu...
PyQt5数据库开发1 4.1... 文章目录 前言 步骤/方法 1 使用windows身份登录 2 启用混合登录模式 3 允许远程连接服...
Android studio ... 解决 Android studio 出现“The emulator process for AVD ...
Linux基础命令大全(上) ♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维...
再谈解决“因为文件包含病毒或潜... 前面出了一篇博文专门来解决“因为文件包含病毒或潜在的垃圾软件”的问题,其中第二种方法有...
南京邮电大学通达学院2023c... 题目展示 一.问题描述 实验题目1 定义一个学生类,其中包括如下内容: (1)私有数据成员 ①年龄 ...
PageObject 六大原则 PageObject六大原则: 1.封装服务的方法 2.不要暴露页面的细节 3.通过r...
【Linux网络编程】01:S... Socket多进程 OVERVIEWSocket多进程1.Server2.Client3.bug&...
数据结构刷题(二十五):122... 1.122. 买卖股票的最佳时机 II思路:贪心。把利润分解为每天为单位的维度,然后收...
浏览器事件循环 事件循环 浏览器的进程模型 何为进程? 程序运行需要有它自己专属的内存空间࿰...
8个免费图片/照片压缩工具帮您... 继续查看一些最好的图像压缩工具,以提升用户体验和存储空间以及网站使用支持。 无数图像压...
计算机二级Python备考(2... 目录  一、选择题 1.在Python语言中: 2.知识点 二、基本操作题 1. j...
端电压 相电压 线电压 记得刚接触矢量控制的时候,拿到板子,就赶紧去测各种波形,结...
如何使用Python检测和识别... 车牌检测与识别技术用途广泛,可以用于道路系统、无票停车场、车辆门禁等。这项技术结合了计...
带环链表详解 目录 一、什么是环形链表 二、判断是否为环形链表 2.1 具体题目 2.2 具体思路 2.3 思路的...
【C语言进阶:刨根究底字符串函... 本节重点内容: 深入理解strcpy函数的使用学会strcpy函数的模拟实现⚡strc...
Django web开发(一)... 文章目录前端开发1.快速开发网站2.标签2.1 编码2.2 title2.3 标题2.4 div和s...