Socket编程实践(3) 多连接服务器实现与简单P2P聊天程序例程

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");elseprintf("连接到服务器的客户端的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<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<signal.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#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<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<signal.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#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;   //协议族为ipv4addr.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");elseprintf("连接到服务器的客户端的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

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

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

相关文章

work2的code和问题

//常量 package cn.itcast.work2; public class Constant { //constant 常量   public static void main(String[] args) { /* * 字符串常量 用双引号括起来的内容 * 整数常量 所有整数 * 小数常量 所有小数 * 字符常量 用单引号括起来的内容,里面只能放单个数字,单个字母或单…

Linux-(C/C++)生成并使用静态库/动态库

静态库/动态库概要 在Windows下静态库的后缀为&#xff1a;.lib、动态库后缀为&#xff1a;.dll&#xff1b;而在Linux下静态库的后缀为&#xff1a;.a、动态库的后缀为&#xff1a;.so。 那么什么是静态库呢&#xff1f; 首先我们来看看程序编译的大体流程&#xff1a;预处理…

windows下最好的围棋_学围棋能使学习成绩提高吗?

孩子几岁开始学习围棋最合适&#xff1f;当然要4岁开始学围棋。一是因为&#xff1a;4岁是幼儿形状知觉形成的关键期&#xff0c;围棋千变万化的棋形(见文后图)是最有利于促进与锻炼孩子形状知觉的形成。二是因为&#xff1a;人的大脑在3周岁后基本已经发育了60%&#xff0c;到…

C#与C/C++的交互zz

C#与C交互&#xff0c;总体来说可以有两种方法&#xff1a; 利用C/CLI作为代理中间层 利用PInvoke实现直接调用 第一种方法&#xff1a;实现起来比较简单直观&#xff0c;并且可以实现C#调用C所写的类&#xff0c;但是问题是MONO构架不支持C/CLI功能&#xff0c;因此无法实现脱…

curl查看swift状态命令_HTTP 请求与响应包括哪些,如何用Chrome查看 HTTP 请求与响应内容和curl 命令的使用...

1.HTTP的请求和响应其实就是通过电脑上的软件来进行的&#xff0c;客户端请求的内容发送到服务器上&#xff0c;服务器收到请求后就会响应客户端的请求&#xff0c;如图&#xff1a;HTTP请求的内容及格式&#xff1a;请求最多包含四部分&#xff0c;最少包含三部分。&#xff0…

Pytorch的BatchNorm层使用中容易出现的问题

前言 本文主要介绍在pytorch中的Batch Normalization的使用以及在其中容易出现的各种小问题&#xff0c;本来此文应该归属于[1]中的&#xff0c;但是考虑到此文的篇幅可能会比较大&#xff0c;因此独立成篇&#xff0c;希望能够帮助到各位读者。如有谬误&#xff0c;请联系指出…

android 比较靠谱的图片压缩

2019独角兽企业重金招聘Python工程师标准>>> 第一&#xff1a;我们先看下质量压缩方法&#xff1a; private Bitmap compressImage(Bitmap image) { ByteArrayOutputStream baos new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG, 100, …

常用公差配合表图_ER弹簧夹头配套BT刀柄常用规格型号表

ER弹簧夹头具有定心精度高&#xff0c;夹紧力均匀的特点&#xff0c;广泛用于机械类零件的精加工和半精加工&#xff0c;通常与BT刀柄匹配使用。BT刀柄是是机械主轴与刀具和其它附件工具连接件&#xff0c;BT为日本标准(MAS403)&#xff0c;现在也是普遍使用的一种标准。传统刀…

Linux下python安装升级详细步骤 | Python2 升级 Python3

Linux下python升级步骤 Python2 ->Python3 多数情况下&#xff0c;系统自动的Python版本是2.x 或者yum直接安装的也是2.x 但是&#xff0c;现在多数情况下建议使用3.x 那么如何升级呢&#xff1f; 下面老徐详细讲解升级步骤&#xff1b; 首先下载源tar包 可利用linux自带下…

华为手机连电脑_手机、电脑无网高速互传!华为神技逆天

Huawei Share是华为的一项自研多终端传输技术&#xff0c;可以在没有网络状态下实现手机与手机、电脑等多终端设备间快速稳定的文件分享&#xff0c;尤其是在办公场景下&#xff0c;可以极大提升办公效率。华为表示&#xff0c;未来Huawei Share将应用于更多全场景跨设备无缝分…

excel统计行数_值得收藏的6个Excel函数公式(有讲解)

收藏的Excel函数大全公式再多&#xff0c;几天不用也会忘记。怎么才能不忘&#xff1f;你需要了解公式的运行原理。小编今天不再推送一大堆函数公式&#xff0c;而是根据提问最多的问题&#xff0c;精选出6个实用的&#xff0c;然后详细的解释给大家。1、计算两个时间差TEXT(B2…

Studio One正版多少钱 Studio One正版怎么购买

随着版权意识的增强&#xff0c;打击盗版的力度越来越大&#xff0c;现在网络上的盗版资源越来越少&#xff0c;资源少很难找是一方面&#xff0c;另一方面使用盗版软件不仅很多功能不能使用&#xff0c;而且很多盗版软件都被植入各种木马病毒&#xff0c;从而带来各种各样的风…

DNS简述

常见DNS记录SOA&#xff1a;域权威开始NS&#xff1a;权威域名服务器A&#xff1a;主机地址CNAME&#xff1a;别名对应的正规名称MX&#xff1a;邮件传递服务器PTR&#xff1a;域名指针 (用于反向 DNS)查询过程浏览器缓存->hosts->LDNS->LDNS缓存->ISP->ISP缓存…

cuda gpu相关汇总

1.Ubuntu16.04:在anaconda下安装pytorch-gpu 转自&#xff1a;Ubuntu16.04:在anaconda下安装pytorch-gpu_莫等闲996的博客-CSDN博客 1 创建虚拟环境并进入 conda create -n pytorch-gpu python3.6 conda activate pytorch-gpu 2 下载对应的安装包和配件 方法一(推荐)&#…

普通人学python有意义吗_学python难吗

首先&#xff0c;对于初学者来说学习Python是不错的选择&#xff0c;一方面Python语言的语法比较简单易学&#xff0c;另一方面Python的实验环境也比较容易搭建。学习Python需要的时间取决于三方面因素。(推荐学习&#xff1a;Python视频教程)其一是学习者是否具有一定的计算机…

在Visual Studio上开发Node.js程序(2)——远程调试及发布到Azure

【题外话】 上次介绍了VS上开发Node.js的插件Node.js Tools for Visual Studio&#xff08;NTVS&#xff09;&#xff0c;其提供了非常方便的开发和调试功能&#xff0c;当然很多情况下由于平台限制等原因需要在其他机器上运行程序&#xff0c;进而需要远程调试功能&#xff0c…

服务器定期监控数据_基础设施硬件监控探索与实践

本文选自 《交易技术前沿》总第三十六期文章(2019年9月)陈靖宇深圳证券交易所 系统运行部Email: jingyuchenszse.cn摘要&#xff1a;为了应对基础设施规模不断上升&#xff0c;数据中心两地三中心带来的运维挑战&#xff0c;深交所结合现有基础设施现状&#xff0c;以通用性、灵…

VS2010问题汇总

问题1&#xff1a;error C3872: "0xa0": 此字符不允许在标识符中使用 error C3872: "0xa0": 此字符不允许在标识符中使用 或者 error C3872: 0xa0: this character is not allowed in an identifier 解法&#xff1a;这是因为直接复制代码的问题。0xa0是…

vue如何获取年月日_好程序员web前端教程分享Vue相关面试题

好程序员web前端教程分享Vue相关面试题&#xff0c;Vue是一套构建用户界面的渐进式框架&#xff0c;具有简单易用、性能好、前后端分离等优势&#xff0c;是web前端工程师工作的好帮手&#xff0c;也是企业选拔人才时考察的重点技能。接下来好程序员web前端教程资源就给大家分享…

react dispatch_React系列自定义Hooks很简单

React系列-Mixin、HOC、Render Props(上)React系列-轻松学会Hooks(中)React系列-自定义Hooks很简单(下)我们在第二篇文章中介绍了一些常用的hooks&#xff0c;接着我们继续来介绍剩下的hooks吧useReducer 作为useState 的替代方案。它接收一个形如(state, action) > newStat…