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");
                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);
                }
        }

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

实现一个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;   //协议族为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;

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序猿DD

Spring Cloud构建微服务架构:分布式服务跟踪(跟踪原理)

通过上一篇《分布式服务跟踪(入门)》的例子,我们已经通过Spring Cloud Sleuth往微服务应用中添加了实现分布式跟踪具备的基本要素。下面通过本文来详...

3395
来自专栏CodingBlock

Android查缺补漏(IPC篇)-- 进程间通讯基础知识热身

本文作者:CodingBlock 文章链接:http://www.cnblogs.com/codingblock/p/8479282.html

1092
来自专栏三丰SanFeng

无锁编程(六) - seqlock(顺序锁)

seqlock(顺序锁) 用于能够区分读与写的场合,并且是读操作很多、写操作很少,写操作的优先权大于读操作。 seqlock的实现思路是,用一个递增的整型数表...

2377
来自专栏Ryan Miao

gradle中使用嵌入式(embedded) tomcat, debug 启动

在gradle项目中使用embedded tomcat。 最开始部署项目需要手动将web项目打成war包,然后手动上传到tomcat的webapp下,然后启动t...

4949
来自专栏Java技术分享

SSM三大框架整合详细总结(Spring+SpringMVC+MyBatis)

使用 SSM ( Spring 、 SpringMVC 和 Mybatis )已经很久了,项目在技术上已经没有什么难点了,基于现有的技术就可以实现想要的功能,当...

1.3K12
来自专栏云原生架构实践

Jhipster技术栈定制 - 基于UAA的微服务之间安全调用

3个微服务都是通过Jhipster生成。 工程代码生成完之后,根据上一节启动的组件的实际情况,修改微服务配置文件中Eureka和database相关的配置。

9983
来自专栏乐沙弥的世界

MySQL "Bind on TCP/IP port: Address already in use"

   最近在已部署MySQL Enterprise Monitor的服务器上新增了MySQL实例,导致MySQL Enterprise Monitor异常宕机...

1231
来自专栏Java3y

Druid数据库连接池就是这么简单

前言 本章节主要讲解Druid数据库连接池,为什么要学Druid数据库连接池呢?? 我的知识储备数据库连接池有两种->C3P0,DBCP,可是现在看起来并不够用...

51611
来自专栏流柯技术学院

linux下安装rzsz

wget http://freeware.sgi.com/source/rzsz/rzsz-3.48.tar.gz

5821
来自专栏逆向技术

调试器编写第一讲,调试器基本框架

                  调试器编写第一讲,调试器基本框架 今天开始调试器第一讲,调试器的基本框架,我们用过很多调试器,比如 WinDbg,Olly...

3006

扫码关注云+社区

领取腾讯云代金券