博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Socket编程实践(3) 多连接服务器实现与简单P2P聊天程序例程
阅读量:4641 次
发布时间:2019-06-09

本文共 5358 字,大约阅读时间需要 17 分钟。

SO_REUSEADDR选项

在上一篇文章的最后我们贴出了一个简单的C/S通信的例程。在该例程序中,使用"Ctrl+c"结束通信后,服务器是无法立即重启的,如果尝试重启服务器,将被告知:

bind: Address already in use

原因在于服务器重新启动时需要绑定地址:

bind (listenfd , (struct sockaddr*)&servaddr, sizeof(servaddr));

而这个时候网络正处于TIME_WAIT的状态,只有在TIME_WAIT状态退出后,套接字被删除,该地址才能被重新绑定。TIME_WAIT的时间是两个MSL,大约是1~4分钟。若每次服务器重启都需要等待TIME_WAIT结束那就太不合理了,好在选项SO_REUSEADDR能够解决这个问题。

服务器端尽可能使用REUSEADD,在bind()之前调用setsockopt来设置SO_REUSEADDR套接字选项,使用SO_REUSEADDR选项可以使不必等待TIME_WAIT状态消失就可以重启服务器。

/*设置地址重复使用*/int on = 1; //on为1表示开启if(setsockopt(listenfp ,SOL_SOCKET,SO_REUSEADDR,&on,sieof(on))<0)    ERR_EXIT("setsockopt error");

处理多客户的服务器

在上一篇文章例程中,服务器端只能够连接一个客户端,并不能处理多个客户端的连接。原因在于服务器使用accept从已连接队列中获取一个连接后,便进入了对该连接的服务中,处于while循环状态。当一个新的客户端连接已经放入已连接队列时,服务器并不能执行到accpet的代码去获取队列中的连接。

为了解决这个问题,我们可以fork()一个子进程,让子进程来处理一个客户端的连接,而父进程循环执行accept的代码,获取新的连接:

int conn ;        while(1)        {                conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);                if(conn <0)                        ERR_EXIT("accept error");                else                       printf("连接到服务器的客户端的IP地址是:%s,端口号是:%d\n",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port));                pid_t pid ;                pid = fork();//创建一个新进程                if (pid ==0) //子进程                {                        close(listenfd); //子进程不需要监听套接字,将其关闭                        /*循环获取数据、发送数据*/                        char recvbuf[1024];                        while(1)                        {                                memset(recvbuf,0,sizeof(recvbuf));                                int ret = read(conn,recvbuf ,sizeof(recvbuf));                                fputs(recvbuf,stdout);                                write(conn,recvbuf,sizeof(recvbuf));                        }                        exit(EXIT_SUCCESS);                }                if(pid >0) //父进程                {                        close(conn);//父进程无需该连接套接字,它的任务是执行accept获取连接                      }                else                 {                        close(conn);                }        }

启动服务器端,使用多个客户端进行连接,可以看到服务器能够同时处理多个连接:

610439-20160426155331830-1286531006.png

实现一个P2P简单聊天程序

为了实现聊天的功能,客户端与服务器端都需要有一个进程来读取连接,另一个进程来处理键盘输入。使用fork()来完成这个简单的聊天程序。

客户端程序:

//p2pcli.c#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m)\ do \ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0)void handler(){ exit(EXIT_SUCCESS);}int main(){ /*创建一个套接字*/ int sock = socket(AF_INET,SOCK_STREAM,0); if(sock == -1) { ERR_EXIT("socket"); } /*定义一个地址结构*/ struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5888); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); /*进行连接*/ if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) { ERR_EXIT("connect"); } else { printf("连接成功\n"); } pid_t pid ; pid = fork(); if(pid == -1) ERR_EXIT("fork"); if(pid == 0) //子进程复制接收数据并显示出来 { char recvbuf[1024]={0}; while(1) { memset(recvbuf,0,sizeof(recvbuf)); int ret = read(sock ,recvbuf,sizeof(recvbuf)); if(ret == -1) { ERR_EXIT("read"); } if(ret == 0) //连接关闭 { printf("连接关闭\n"); kill(getppid(),SIGUSR1); break; } else { printf("接收到信息:"); fputs(recvbuf,stdout); } } } else //父进程负责从键盘接收输入并发送 { signal(SIGUSR1,handler); char sendbuf[1024]={0} ; while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { write(sock,sendbuf,strlen(sendbuf)); memset(&sendbuf,0,sizeof(sendbuf)); } } close(sock); return 0;}

服务器端程序:

// p2pser.c#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m)\ do \ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0)/*信号处理函数*/void handler(int sig){ exit(EXIT_SUCCESS);}int main(){ /* 创建一个套接字*/ int listenfd= socket(AF_INET ,SOCK_STREAM,0); if(listenfd==-1) ERR_EXIT("socket"); /*定义一个地址结构并填充*/ struct sockaddr_in addr; addr.sin_family = AF_INET; //协议族为ipv4 addr.sin_port = htons(5888); //绑定端口号 addr.sin_addr.s_addr = htonl(INADDR_ANY);//主机字节序转为网络字节序 /*重复使用地址*/ int on = 1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) { ERR_EXIT("setsockopt"); } /*将套接字绑定到地址上*/ if(bind(listenfd,(const struct sockaddr *)&addr ,sizeof(addr))==-1) { ERR_EXIT("bind"); } /*监听套接字,成为被动套接字*/ if(listen(listenfd,SOMAXCONN)<0) { ERR_EXIT("Listen"); } struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); int conn ; conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen); if(conn <0) ERR_EXIT("accept error"); else printf("连接到服务器的客户端的IP地址是:%s,端口号是:%d\n",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port)); pid_t pid ; pid = fork();//创建一个新进程 if(pid == -1) { ERR_EXIT("fork"); } if(pid == 0)//子进程 { signal(SIGUSR1,handler); char sendbuf[1024] = {0}; while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { write(conn,sendbuf,sizeof(sendbuf)); memset(sendbuf,0,sizeof(sendbuf)); } exit(EXIT_SUCCESS); } else //父进程 用来获取数据 { char recvbuf [1024]={0}; while(1) { memset(recvbuf,0,sizeof(recvbuf)); int ret = read(conn ,recvbuf,sizeof(recvbuf)); if(ret == -1) { ERR_EXIT("read"); } if(ret == 0) //对方已关闭 { printf("对方关闭\n"); break; } fputs(recvbuf,stdout); } kill(pid,SIGUSR1); exit(EXIT_SUCCESS); } /*关闭套接字*/ close(listenfd); close(conn); return 0;

转载于:https://www.cnblogs.com/QG-whz/p/5435396.html

你可能感兴趣的文章
bzoj3238 [Ahoi2013]差异
查看>>
ASP.NET常见面试题及答案(130题)
查看>>
初学CDQ分治-NEU1702
查看>>
React组件的生命周期
查看>>
java笔记--使用SwingWoker类完成耗时操作
查看>>
Android应用程序后台加载数据
查看>>
2016北京集训测试赛(九)Problem C: 狂飙突进的幻想乡
查看>>
CentOS6.5手动升级gcc4.8.2
查看>>
3.9 java基础总结集合①LIst②Set③Map④泛型⑤Collections
查看>>
Unix和Linux下C语言学习指南
查看>>
linux指令
查看>>
linux下面升级 Python版本并修改yum属性信息
查看>>
局域网内通讯APP
查看>>
Unity Shader 图片流光效果实现(纯计算方式)
查看>>
POJ 2002 Squares
查看>>
Java 内存分配
查看>>
ObjectDataSource控件执行Delete操作时,出现“未能找到带参数的非泛型方法”的解决方案...
查看>>
Ubuntu17.10 React Native 环境搭建
查看>>
Atitit 基于sql编程语言的oo面向对象大规模应用解决方案attilax总结
查看>>
jQuery-2.1.4.min.js:4 Uncaught TypeError: Illegal invocation
查看>>