今天继续对socket编程进行学习,在学习之前,需要回顾一下上一篇中编写的回射客户/服务器程序(http://www.cnblogs.com/webor2006/p/3923254.html),因为今天的知识点需要基于它来进行说明,下面来回顾一下关键代码:

对于服务器端:echosrv.c

对于客户端:echocli.c

下面通过一个简单的图来描述一下其关系:

可想而知,这两个套接字都有自己的地址,对于conn服务端而言,它的地址是在绑定的时候确认的,也就是:

而对于sock客户端而言,它的地址是在连接成功时确定的。一旦连接成功,则会自动选择一个本机的地址和端口号,当一个客户端连接服务器成功时,在服务器端是可以打印出客户端的地址和端口信息的,具体代码如下:

所以可以将其客户端的地址和端口号打印出来,修改服务端代码如下:

这次编译运行看下效果:

当客户端连接成功时,则在服务端将其客户端的ip和端口号打印出来了。

接下来,要解决一个上篇博文中遇到的问题,问题现象就是如下:

原因是由于,重启时会重新再绑定,而此时该服务器是处于TIME_WAIT状态,通过命令可以查看到该状态:

【注意】:TIME_WAIT状态,需服务器与客户端都已经退出来才会出来。

而该状态下,默认是无法再次绑定的,那如何解决此问题呢?可以使用SO_REUSEADDR选项来解决此问题:

具体使用方法如下,修改服务端代码如下:

这时再来看是否解决了此问题:

但是,这个错误还会在一种场景下报出来,如下:

下面再来看一下目前程序的问题:目前服务器只能接收一个客户端的连接,看下面:

分析一下服务端的代码就可以得知:

解决这个问题的思路就是:一个连接一个进程来处理并发(process-per-connection),也就是上面画红圈的放到子进程去处理,然后主进程可以去accept客户端的请求了,具体代码修改如下:

echosrv.c:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() void do_service(int conn)//将处理客户端请求数据逻辑封装到一个函数中,这样代码更加模块化
{
char recvbuf[1024];
while (1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));
fputs(recvbuf, stdout);
write(conn, recvbuf, ret);
}
} int main(void)
{
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < )
/* if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
/*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
/*inet_aton("127.0.0.1", &servaddr.sin_addr);*/ int on = ;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < )
ERR_EXIT("setsockopt"); if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) < )
ERR_EXIT("listen"); struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn; pid_t pid;
while (1)//用一个循环来不断处理客户端的请求,所以将accept操作也放到循环中
{
if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
ERR_EXIT("accept"); printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); pid = fork();//创建进程
if (pid == -1)
ERR_EXIT("fork");
if (pid == 0)
{//子进程来处理与客户端的数据
close(listenfd);//对于子进程而言,监听listenfd套接字没用,则直接关闭掉,注意:套接字在父子进程是共享的
do_service(conn);//开始处理请求数据
}
else//父进程则回到while循环头,去accept新的客户端的请求,这样就比较好的解决多个客户端的请求问题
close(conn);
} return ;
}

下面来看下效果:

对于这段程序,其实还需要完善,也就是不能监听客户端的退出,

那怎么监听客户端的监听呢?

编译运行看一下效果:

从中可以看到,当客户端退出时,服务端也收到消息了。

下面用多进程方式实现点对点聊天来进一步理解套接字编程,这里实现的聊天程序是这样:

那不管对于服务端,还是客户端,应该每个端点都有有两个进程,一个进程读取对等方的数据,另一个进程专门从键盘中接收数据发送给对待方,这里实现的只是一个服务端跟一个客户端的通讯,不考虑一个服务端跟多个客户端的通讯了,重在练习,下面还是基于之前的代码开始实现:

p2psrv.c【点对点通信服务端】:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(void)
{
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < )
/* if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
/*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
/*inet_aton("127.0.0.1", &servaddr.sin_addr);*/ int on = ;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < )
ERR_EXIT("setsockopt"); if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) < )
ERR_EXIT("listen"); struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn;
if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < )
ERR_EXIT("accept"); printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork"); if (pid == 0)
{//子进程用来从键盘中获取输入输数据向客户端发送数据
char sendbuf[1024] = {0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
write(conn, sendbuf, strlen(sendbuf));
memset(sendbuf, 0, sizeof(sendbuf));
}
exit(EXIT_SUCCESS);
}
else
{//父进程读取从客户端发送过来的数据
char recvbuf[1024];
while (1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));
if (ret == -1)
ERR_EXIT("read");
else if (ret == 0)
{
printf("peer close\n");
break;
} fputs(recvbuf, stdout);
}
exit(EXIT_SUCCESS);
} return ;
}

基本上前面的listen、bind、accept的代码没动,编译一下:

木有问题~~~

p2pcli.c【点对点通信客户端】:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < )
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
ERR_EXIT("connect"); pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork"); if (pid == 0)
{//子进程接收来自服务端发来的数据
char recvbuf[1024];
while (1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = read(sock, recvbuf, sizeof(recvbuf));
if (ret == -1)
ERR_EXIT("read");
else if (ret == 0)
{
printf("peer close\n");
break;
} fputs(recvbuf, stdout);
}
close(sock);
}
else
{//父进程获取从键盘中敲入的命令向服务端发送数据
char sendbuf[1024] = {0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
write(sock, sendbuf, strlen(sendbuf));
memset(sendbuf, 0, sizeof(sendbuf));
}
close(sock);
}
return ;
}

编译运行:

可见就实现了点对点的聊天程序。这时客户端关闭时,服务端也关闭了,但是,实际上服务端的程序还是有些问题的,分析一下代码:

可以在父子进程退出时都打印一个log来验证下:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(void)
{
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < )
/* if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
/*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
/*inet_aton("127.0.0.1", &servaddr.sin_addr);*/ int on = ;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < )
ERR_EXIT("setsockopt"); if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) < )
ERR_EXIT("listen"); struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn;
if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < )
ERR_EXIT("accept"); printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); pid_t pid;
pid = fork();
if (pid == -)
ERR_EXIT("fork"); if (pid == )
{
char sendbuf[] = {};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
write(conn, sendbuf, strlen(sendbuf));
memset(sendbuf, , sizeof(sendbuf));
}
printf("child close\n");
exit(EXIT_SUCCESS);
}
else
{
char recvbuf[];
while ()
{
memset(recvbuf, , sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));
if (ret == -)
ERR_EXIT("read");
else if (ret == )
{
printf("peer close\n");
break;
} fputs(recvbuf, stdout);
}
printf("parent close\n");
exit(EXIT_SUCCESS);
} return ;
}

而此时,如果我按任意一个字符,则子进程就退出来:

那怎么解决当父进程退出时,也让其子进程退出呢,就可以用到我们之前学过的信号来解决了,具体如下:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() void handler(int sig)//当子进程收到信号时,则将自身退出
{
printf("recv a sig=%d\n", sig);
exit(EXIT_SUCCESS);
} int main(void)
{
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < )
/* if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
/*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
/*inet_aton("127.0.0.1", &servaddr.sin_addr);*/ int on = ;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < )
ERR_EXIT("setsockopt"); if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) < )
ERR_EXIT("listen"); struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn;
if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < )
ERR_EXIT("accept"); printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); pid_t pid;
pid = fork();
if (pid == -)
ERR_EXIT("fork"); if (pid == )
{
signal(SIGUSR1, handler);//子进程注册一个SIGUSR1信号,以便在父进程退出时,通知子进程退出
char sendbuf[] = {};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
write(conn, sendbuf, strlen(sendbuf));
memset(sendbuf, , sizeof(sendbuf));
}
printf("child close\n");
exit(EXIT_SUCCESS);
}
else
{
char recvbuf[];
while ()
{
memset(recvbuf, , sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));
if (ret == -)
ERR_EXIT("read");
else if (ret == )
{
printf("peer close\n");
break;
} fputs(recvbuf, stdout);
}
printf("parent close\n");
kill(pid, SIGUSR1);//当父进程退出时,发送一个SIGUSR1信号
exit(EXIT_SUCCESS);
} return ;
}

这时再看下效果:

如果反过来呢,将服务端关闭,那客户端程序也会关闭么?

可以看到,当服务端退出时,客户端并没有退出,那是啥原因呢?

那解决此问题也是可以利用信号来解决,就像服务端一样,可以在子进程退出时,向父进程发送一个信号,然后父进程也退出既可,修改代码如下:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() void handler(int sig)
{
printf("recv a sig=%d\n", sig);
exit(EXIT_SUCCESS);
} int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < )
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
ERR_EXIT("connect"); pid_t pid;
pid = fork();
if (pid == -)
ERR_EXIT("fork"); if (pid == )
{
char recvbuf[];
while ()
{
memset(recvbuf, , sizeof(recvbuf));
int ret = read(sock, recvbuf, sizeof(recvbuf));
if (ret == -)
ERR_EXIT("read");
else if (ret == )
{
printf("peer close\n");
break;
} fputs(recvbuf, stdout);
}
close(sock);
kill(getppid(), SIGUSR1);
}
else
{
signal(SIGUSR1, handler);
char sendbuf[] = {};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
write(sock, sendbuf, strlen(sendbuf));
memset(sendbuf, , sizeof(sendbuf));
}
close(sock);
} return ;
}

这里就不多解释了,跟服务端的道理一样,这样再来运行一下看下效果:

这样,就实现了一个点对点的聊天的程序,当然,这里没有考虑到多个客户端与服务端通讯的问题,但是根据这个程序也能容易进行扩展,今天的内容就先学到这,下次再见~~~

linux网络编程之socket编程(三)的更多相关文章

  1. linux网络编程之socket编程(一)

    今天开始,继续来学习linux编程,这次主要是研究下linux下的网络编程,而网络编程中最基本的需从socket编程开始,下面正式开始学习: 什么是socket: 在学习套接口之前,先要回顾一下Tcp ...

  2. linux网络编程之socket编程(六)

    经过一个国庆长假,又有一段时间没有写博文了,今天继续对linux网络编程进行学习,如今的北京又全面进入雾霾天气了,让我突然想到了一句名句:“真爱生活,珍惜生命”,好了,言归正传. 回顾一下我们之间实现 ...

  3. linux网络编程之socket编程(四)

    经过两周的等待,终于可以回归我正常的学习之旅了,表哥来北京了在我这暂住,晚上回家了基本在和他聊天,周末带他在北京城到处乱转,几乎剥夺了我自由学习的时间了,不过,亲人之情还是很难得的,工作学习并不是生活 ...

  4. linux网络编程之socket编程(八)

    学习socket编程继续,今天要学习的内容如下: 先来简单介绍一下这五种模型分别是哪些,偏理论,有个大致的印象就成,做个对比,因为最终只会研究一个I/O模型,也是经常会用到的, 阻塞I/O: 先用一个 ...

  5. linux网络编程之socket编程(二)

    今天继续对socket编程进行研究,这里会真正开如用socket写一个小例子,进入正题: TCP客户/服务器模型:   关于这个模型的流程这里就不多说了,比较容易理解,下面则利用这种模型来编写一个实际 ...

  6. linux网络编程之socket编程(十一)

    今天继续学习socket编程,这次主要是学习超时方法的封装,内容如下: ①.alarm[不常用,了解既可] 它的实现思路是这样的: 但是这种方案有一定的问题,因为闹钟可能会作为其它的用途,这时所设置的 ...

  7. linux网络编程之socket编程(七)

    今天继续学习socket编程,北京在持续几天的雾霾天之后久违的太阳终于出来了,心情也特别特别的好,于是乎,在这美好的夜晚,该干点啥事吧,那当然就是继续坚持我的程序学习喽,闲话不多说,进入正题: 通过这 ...

  8. linux网络编程之socket编程(十六)

    继续学习socket编程,今天的内容会有些难以理解,一步步来分解,也就不难了,正入正题: 实际上sockpair有点像之前linux系统编程中学习的pipe匿名管道,匿名管道它是半双工的,只能用于亲缘 ...

  9. linux网络编程之socket编程(十五)

    今天继续学习socket编程,这次主要是学习UNIX域协议相关的知识,下面开始: [有个大概的认识,它是来干嘛的] ①.UNIX域套接字与TCP套接字相比较,在同一台主机的传输速度前者是后者的两倍. ...

随机推荐

  1. [LeetCode] 844. Backspace String Compare 退格字符串比较

    Given two strings S and T, return if they are equal when both are typed into empty text editors. # m ...

  2. Linq查询连接guid与varchar字段

    使用场景 在数据库设计中进场会出现一些通用表,如通用附件表,一般都是通过ForeignTable(关联的表名)和ForeignKey(关联表的主键)与其他表关联.这样的表在数据库中没有外键关系,而且一 ...

  3. jquery on绑定事件叠加解决方法

    jquery on绑定事件叠加解决方法 代码如下 <pre> $('.maoqiu').each(function () { var is_bind = $(this).attr('is_ ...

  4. python random模块(14)

    random 模块包括返回随机数的函数,可以用于模拟或者任何产生随机输出的程序. 一.random模块常用函数介绍 random.random() — 生成一个从0.0(包含)到 1.0(不包含)之间 ...

  5. vcf和bed的位置信息区别

    vcf和bed的位置信息区别 vcf和gff一般是从1-base开始计数,也就是文件里所代表的的位置是染色体的真实位置 bed文件一般所代表的位置是从0开始计数的,是一个半闭合区间,也就是(0,200 ...

  6. # Java类链接模型

    动态链接和解析 每一个class都有一个常量池, 保存它自己的所有的符号引用. 每一个已经被加载的class, interface都另外有一个内部版本的常量池, 叫做runtime constant ...

  7. 【1】BIO与NIO、AIO的区别

    一.BIO 在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求 ...

  8. harbor helm仓库使用

    harbor helm仓库使用 官方文档地址:https://github.com/goharbor/harbor Monocular 从1.0 开始专注于helm 的UI展示,对于部署以及维护已经去 ...

  9. CSS 各种形状

    制作圆形: 要使用CSS来制作一个圆形,我们需要一个div,被给它设置一个ID <div id="circle"></div>  圆形在设置CSS时要设置宽 ...

  10. 关于 Nginx的相关学习

    转自:https://www.cnblogs.com/wcwnina/category/1193394.html Nginx能做什么 ——反向代理 ——负载均衡 ——HTTP服务器(动静分离) ——正 ...