56.1.概述
- socket 是一种IPC方法,允许位于同一主机或使用网络连接起来的不同主机上的应用程序之间交换数据。 UNIX
允许位于同一主机系统上的应用程序之间通信 Internet domain IPv4 and IPV6 // socket
通信方式
1.各个应用程序创建一个socket,socket是一个允许通信的“设备”, 两个应用程序都需要用到它
2.服务器将自己的socket绑定到一个众所周知的地址上使得客户端能够定位到它的位置。 socket文件描述符 fd = socket(domain, type, protocol); protocol = 0 通信 domain :
1.识别出一个 socket 方法(即 socket "地址"的格式);
2.通信范围(位于同一主机上, 还是位于使用一个网络连接起来的不同主机上的应用程序)
1.UNIX(AF_UNIX) domain 位于同一主机上
2.IPv4 (AF_INET) 网络连接
3.IPv6 (AF_INET6) 网络连接 AF 表示 “地址族”, PF 表示 “协议族” socket 类型:
1.流
1.可靠的:保证发送–>数据完整无缺–>接受 或 收到传送失败的通知
2.双向的:可在两个socket之间的任意方向上传输
3.字节流:和管道一样不存在消息边界的概念
2.数据报(SOCK_DGRAM):允许数据以被称为数据报的消息的形式进行交换。消息边界得到保留,数据传输不可靠,消息到达可能无序,重复或未到达
socket系统调用 socket(); // 创建一个新 socket bind(); // 将一个 socket
绑定到一个地址 listen(); // 允许一个流socket接受来自其他socket的接入连接 accept(); //
在一个流监听socket上接受来自一个对等应用程序的连接,并可选地返回对等socket的地址 connect(); // 建立与另一个
socket 之间的连接 close(); socket I/O 可以使用传统read(), write()系统调用。 系统调用在I
/O操作无法被立即完成时会阻塞 fcntl()F_SETFL操作启用O_NONBLOCK打开文件状态标记可以执行非阻塞 socket
特有 send(),recv(),sendto(),recvfrom()。
56.2 创建一个socket:socket()
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain: 指定socket的通信domain
type: 指定socket类型 SOCK_STREAM
protocol = 0
成功返回一个引用在后续系统调用中会用到的新创建的socket的文件描述符
56.3 将socket绑定到地址: bind()
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr * addr, socklen_t addrlen);
sockfd: socket()调用中获得的文件描述符
addr:是指针,指向一个指定该socket绑定到的地址的结构
addrlen:指定地址结构的大小
除了将一个服务器的 socket 绑定到一个众所周知的地址之外,还有其他做法。例如,对于一个Internet domain socket 来讲,服务器可以不调用 bind() 而直接调用 listen(), 这将会导致内核为改 socket 选择一个临时端口。之后服务器可以使用 getsockname() 来获取 socket 地址。在这种场景中,服务器必须要发布其地址使得客户端能够知道如何定位到服务器的socket。
56.4 通用socket地址结构:struct sockaddr
UNIX domain socket 使用路径名
Internet domain socket 使用IP地址和端口号。
struct sockaddr 唯一用途: 将各种domain特定的地址结构转换成单个类型以供socket 系统调用中的各个参数使用
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
};
UNIX还有额外的字段sa_len, 指定这个结构的总大小。
56.5 流socket
流socket的运作与电话系统类似。
socket()系统调用将会创建一个socket,这等于安装一个电话,为使两个应用程序能够通信,每个应用程序都必须要创建一个socket.
通过一个流socket同行类似于电话呼叫。一个应用程序在进行通信之前必须要将其socket连接到另一个应用程序的socket上。两个socket的连接过程如下
(a)一个应用程序调用bind()以将socket绑定到一个众所周知的地址上,然后调用listen()t通知内核它接受接入连接的意愿。这一步类似于已经有一个为众人所知的电话号码并确保打开了电话,这样人们就可以打进电话了
(b)其他应用程序通过connect()建立连接,同时指定socket的地址,这类似于拨某人的电话号码。
(c)调用listen()的应用程序使用accect()接受连接。这类似于在电话想起时拿起电话。如果在对等应用程序调用connect()之前指定了accecpt(),那么accecpt()就会阻塞。
- 一旦建立了连接之后就可以在两个应用程序之间(类似于两路电话会话)进行双向数据传输知道其中一个使用close()关闭为止。通信是通过read()和write()系统调用或通过一些提供了额外功能的socket特定的系统调用(send() 和recv())来完成的。下图演示流socke如何t使用这些系统调用
主动 socket 和 被动 socket:
1.在默认情况下,使用 socket() 创建的 socket 是主动的。一个主动的 socket 可用在 connect() 调用来建立一个到被动 socket 的连接。这种行为称为执行一个主动的打开。
2.一个被动的 socket(也被称为一个监听socket)是一个通过调用listen()以被标记成允许接入连接socket。接受一个接入连接通常被称为执行一个被动的打开。
在大多数情况下,服务器执行一个被动打开,而客户端执行主动打开。
56.5.1 监听接入连接:listen()
listen()系统调用将文件描述符sockfd引用的流socket标记为被动。这个socket后面会被用来接受其他(主动的)socket连接
#include <sys/socket.h>
int listen(int sockfd,int backlog)
无法在一个已经连接的socket(即已经成功执行connect()的socket或由accept()调用返回的socket)上执行listen()。
要理解backlog参数的用途首先需要注意到客户端可能会在服务器调用accept()之前调用connect().这种情况下有可能会发生的,如服务器可能正在忙于处理其他客户端,这将会长生一个未决的连接。如下图所示
**56.2 创建一个socket:socket()**
内核必须要记录所有味觉的连接请求的相关信息,这样后续的accept()就能够处理这些请求了。backlog参数允许限制这种未决连接的数量,在这个限制内的连接请求会立即成功。之外的连接请求就会阻塞直到一个未决的连接被接受(通过accept(),)并从未决连接队列删除为止。
在Linux上,这个常量的值被定义成了128,但从内核2.4.5起,Linux允许在运行时通过Linux特有的/proc/sys/core/somaxconn 文件来调整这个值
(base) wannian07@wannian07-PC:/proc/sys/net/core$ gedit somaxconn
512
理解 Linux backlog/somaxconn 内核参数
https://jaminzhang.github.io/linux/understand-Linux-backlog-and-somaxconn-kernel-arguments/
各参数的含义:https://www.alibabacloud.com/help/zh/faq-detail/41334.htm
理解 Linux backlog/somaxconn 内核参数
Linux LinuxTCPSocketbacklogsomaxconn
引言
之前负载均衡超时问题这篇博文中提到一个可能原因是:
后端服务器 Socket accept 队列满,系统的 somaxconn 内核参数默认太小。
学习理解下 somaxconn 内核参数相关内容。
TCP SYN_REVD, ESTABELLISHED 状态对应的队列
TCP 建立连接时要经过 3 次握手,在客户端向服务器发起连接时,
对于服务器而言,一个完整的连接建立过程,服务器会经历 2 种 TCP 状态:, ESTABELLISHED。
对应也会维护两个队列:
- 一个存放 SYN 的队列(半连接队列)
- 一个存放已经完成连接的队列(全连接队列)
当一个连接的状态是 SYN RECEIVED 时,它会被放在 SYN 队列中。
当它的状态变为 ESTABLISHED 时,它会被转移到另一个队列。
所以后端的应用程序只从已完成的连接的队列中获取请求。
如果一个服务器要处理大量网络连接,且并发性比较高,那么这两个队列长度就非常重要了。
因为,即使服务器的硬件配置非常高,服务器端程序性能很好,
但是这两个队列非常小,那么经常会出现客户端连接不上的现象,
因为这两个队列一旦满了后,很容易丢包,或者连接被复位。
所以,如果服务器并发访问量非常高,那么这两个队列的设置就非常重要了。
Linux backlog 参数意义
对于 Linux 而言,基本上任意语言实现的通信框架或服务器程序在构造 socket server 时,都提供了 backlog 这个参数,
因为在监听端口时,都会调用系统底层 API: int listen(int sockfd, int backlog);
listen 函数中 backlog 参数的定义如下:
Now it specifies the queue length for completely established sockets waiting to be accepted,
instead of the number of incomplete connection requests.
The maximum length of the queue for incomplete sockets can be set using the tcp_max_syn_backlog sysctl.
When syncookies are enabled there is no logical maximum length and this sysctl setting is ignored.
If the socket is of type AF_INET, and the backlog argument is greater than the constant SOMAXCONN(128 default),
it is silently truncated to SOMAXCONN.
backlog 参数描述的是服务器端 TCP ESTABELLISHED 状态对应的全连接队列长度。
全连接队列长度如何计算?
如果 backlog 大于内核参数 net.core.somaxconn,则以 net.core.somaxconn 为准,
即全连接队列长度 = min(backlog, 内核参数 net.core.somaxconn),net.core.somaxconn 默认为 128。
这个很好理解,net.core.somaxconn 定义了系统级别的全连接队列最大长度,
backlog 只是应用层传入的参数,不可能超过内核参数,所以 backlog 必须小于等于 net.core.somaxconn。
半连接队列长度如何计算?
半连接队列长度由内核参数 tcp_max_syn_backlog 决定,
当使用 SYN Cookie 时(就是内核参数 net.ipv4.tcp_syncookies = 1),这个参数无效,
半连接队列的最大长度为 backlog、内核参数 net.core.somaxconn、内核参数 tcp_max_syn_backlog 的最小值。
即半连接队列长度 = min(backlog, 内核参数 net.core.somaxconn,内核参数 tcp_max_syn_backlog)。
这个公式实际上规定半连接队列长度不能超过全连接队列长度。
其实,对于 Nginx/Tomcat 等这种 Web 服务器,都提供了 backlog 参数设置入口,
当然它们都会有默认值,通常这个默认值都不会太大(包括内核默认的半连接队列和全连接队列长度)。
如果应用并发访问非常高,只增大应用层 backlog 是没有意义的,因为可能内核参数关于连接队列设置的都很小,
一定要综合应用层 backlog 和内核参数一起看,通过公式很容易调整出正确的设置。
linux中tcp连接内核参数调优somaxconn
https://blog.csdn.net/jackyechina/article/details/70992308
永久生效:
vim /etc/sysctl.conf
net.core.somaxconn=32768
sysctl -p
立即生效:
sysctl -w net.core.somaxconn=32768
重启应用生效
sysctl -a显示所有内核参数
看其解析:
对于一个TCP连接,Server与Client需要通过三次握手来建立网络连接.当三次握手成功后,
我们可以看到端口的状态由LISTEN转变为ESTABLISHED,接着这条链路上就可以开始传送数据了.
每一个处于监听(Listen)状态的端口,都有自己的监听队列.监听队列的长度,与如下两方面有关:
- somaxconn参数.
- 使用该端口的程序中listen()函数.
关于somaxconn参数:
定义了系统中每一个端口最大的监听队列的长度,这是个全局的参数,默认值为128.限制了每个端口接收新tcp连接侦听队列的大小。
对于一个经常处理新连接的高负载 web服务环境来说,默认的 128 太小了。大多数环境这个值建议增加到 1024 或者更多。
服务进程会自己限制侦听队列的大小(例如 sendmail(8) 或者 Apache),常常在它们的配置文件中有设置队列大小的选项。大的侦听队列对防止拒绝服务 DoS 攻击也会有所帮助。
56.5.2 接受连接:accept()
accept()系统调用在文件描述符sockfd引用的监听流socked上接受一个接入连接。如果在调用accept()时不存在未决的连接,那么调用就会阻塞到直到有链接请求为止,
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t addrlen);
理解accept()的关键点是它会创建一个新socket,并且正是这个新socket会与执行connect()的对等socket进行连接。accept()调用返回的函数结果是已连接的socket的文件描述符。监听socket(sockfd)会保持打开状态,并且可以被用来接受后续的连接,一个典型的服务器应用程序会创建一个监听socket,将其绑定到一个众所周知的地址上,然后通过接受该socket上的连接来处理所有客户端请求。
传入accept()的剩余参数会返回虽短的socket的地址,addr参数指向了一个用来返回socket地址的结构。这个参数的类型取决于socket domain(与bind一样)
addrlen参数是一个值–结果参数。它指向一个整数,在调用被执行之前必须要将这个整数初始化为addr指向的缓冲区的大小,这样内核就知道有多少空间可用于返回socket地址了。当accept()返回之后,这个整数会被设置成实际被复制进缓冲区的数据的字节数。
如果不关心对等socket的地址,那么可以将addr和addrlen分别指定为NULL和0。
把套接口地址结构传递给套接口函数时,总是通过指针来传递的,即传递的是一个指向结构的指针。结构的长度也作为参数来传递,其传递方式取决于结构的传递方向:从进程到内核,还是从内核到进程。
1、从进程到内核传递套接口地址结构有3个函数:bind、connect和sendto,这3个函数的一个参数是指向套接口地址结构的指针,另一个是结构的整数大小,例如:
struct sockaddr_in serv;
/ fill in serv{} /
connect( sockfd, (SA)&serv, sizeof(serv) );
由于指针和指针所指结构的大小都传递给内核,所以从进程到内核要确切拷贝多少数据是知道的。
套接口地址结构大小的数据类型确切地说应该是socklen_t,而不是int,POSIX建议将socklen_t定于为uint32_t。
2、从内核到进程传递套接口地址结构有四个函数:accept、recvfrom、getsockname 和 getpeername。这4个函数的两个参数是:指向套接口地址结构的指针和指向表示结构大小的整数的指针,例如:
struct sockaddr_un cli; /* Unix domain /
socklen_t len;
len = sizeof(cli);
getpeername(unixfd, (SA) &cli, &len )
/* len may have changed */
为何将结构大小由整数改为指向整数的指针呢?这是因为:当函数被调用时,结构大小是一个值(value, 此值告诉内核该结构的大小,使内核在写此结构时不至于越界),当函数返回时,结构大小又是一个结果(result,它告诉进程内核在此结构中确切存储了多少信息),这种参数叫做值-结果参数(value-result)。
当使用值-结果参数作为套接口地址结构的长度时,如果套接口地址结构是定长的,则从内核返回的值也是定长的,如对于IPv4,sockaddr_in是16;对于IPv6,sockaddr_in6是28。但对于变长的套接口地址结构(如Unix域的sockaddr_un),返回值可能比结构的最大长度小。
http://www.cnblogs.com/ChangeIt/archive/2011/12/26/2302294.html
56.5.3 连接到对等socket :connect()
connect()系统调用将文件描述符socketfd引用的主动socket连接到地址通过addr和addrlen指定的监听socket上。
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
addrhe addrlen 参数的指定方式与bind()调用中对应参数的指定方式相同。
如果connect()失败并且希望重新进行连接,那么SUSv3规定完成这个任务的可移植的方法是关闭这个socket,创建一个新socket,在该新socket上进行重新连接。
56.5.4 流socket I/O
一对连接的流socket在两个端点之间提供了一个双向通信信道,下图给出了UNIX domain 的情形
连接流socket上I/O的语义与管道上I/O的语义类似
要执行I/O需要使用read()和write()系统调用(或使用socket特有的send()和recv调用)。由于socket是双向的,因此在连接两端都可以使用这个调用
一个socket可以使用close()系统调用来关闭或在应用程序终止之后关闭。之后对等的应用程序试图从链接的另一端读取数据时将会收到文件结束(当所有缓冲都被读取之后)。如果对等应用程序试图向其socket写入数据,那么他会收到一个SIGPIPE信号,并且系统调用回单会EPIPE错误。
56.5.5 连接终止 close()
终止一个流socket连接的常见方式时调用close(),如果多个文件描述符引用了同一个socket,那么当所有描述符被关闭之后连接就会终止。
56.6 数据报socket
数据报socket的运作类似于邮政系统。
1.socket()系统调用等价于创建一个邮箱。(取信和送信都是从信箱中发生的。)所有需要发送和接受数据报的应用程序都需要使用socket()创建一个数据报socket。
2. 为允许另一个应用程序发送数据包(信),一个应用程序需要使用bind(0将其socket绑定到一个众所周知的地址上。一般来讲,一个服务器会将其socket绑定到一个众所周知的地址上,而一个客户端会通过向该地址发送一个数据报来发起通信。(特别是UNIX domain–客户端如果先要接受服务器发送来的数据报的话可能还需要使用bind()将一个地址赋给其socket.)
3. 要发送一个数据报,一个应用程序需要调用snedto(),他接收的其中一个参数是数据包发送的socket地址,这类似于将收信人的地址写到信件上并投递这封信。
4. 为接收一个数据报,一个应用程序需要调用recvfrom(),它在没有数据报到达时会阻塞。由于数recvfrom()允许获取发送者的地址,因此可以在需要的时候发送一个响应。(这在发送这的socket没有绑定到一个众所周知的地址上时是有用的,客户端通常会发生这种情况。)因为已投递的信件上时无需标记上发送者的地址的。
5. 当不在需要socketd时,应用程序需要close()关闭socket.
下图演示了数据报socket相关系统调用
56.6.1 交换数据报:recvfrom和sendto()
recvfrom()和sendto系统调用在一个数据报socket上接受和发送数据报
#include <sys/socket.h>
ssize_t recvfrom(int sockfd,void *buffer,size_t length,int flags,struct sockaddr *src_addr,socklen_t *addrlen);
ssize_t sendto(int sockfd,const void *buffer,size_t length,int flags,const struct sockaddr *dest_addr,socklen_t addrlen);
这两个系统调用的返回值和前三个参数与read()和write()中的返回值和相应参数是一样的。
第四个参数flags是一个位掩码,它控制着了socket特定的I/O特性,如果无需使用其中任何一种特性,那么可以将flags指定为0。
src_addr和addrlen参数被用来获取指定与之通信的对等socket的地址。
对于recvfrom()来讲,src_addr和addrlen参数会返回用来发送数据报的远程的socket的地址。src_addr参数是一个指针,它指向了一个与通信domain匹配的地址结构。与accept()一样,addrlen是一个值-结果参数。在调用之前应该将addrlen初始化为src_addr指向的结构的大小;在返回之后,它包含了实际写入这个结构的字节数。
如果不关心发送者的地址,那么可以将src_addr和addrlen都指定为NULL.在这种情况下recvfrom()等价recv()来接收一个数据报。也可以使用read()来读取一条数据报,者等价于在使用recv()时将flags参数指定为0.
不管length参数值是什么,recvfrom只会从一个数据报socket中读取一条消息,如果消息的大小超过了length字节,那么消息会被静默地截断为length字节。
对于sendto()来讲,dest_addr和addrlen参数指定了数据报发送地socket,这些参数地使用方式与connect()中相应参数地使用方式是一样地。dest参数是一个与通信domain匹配地地址结构,它会被初始化成目标socket地地址,addrlen参数指定了addr的大小。
56.6.2 在数据报socket上使用connect()
尽管数据报socket是无连接的,但在数据报上应用connect()系统调用仍然是起作用的。在数据报socket上调用connect()会导致内核记录这个socket的对等的socket的地址。术语已连接的数据报socket就是指此种socket.
术语非连接的数据报socket是指那些没有调用connect()的数据报socket(即新数据报的默认行为)。
当一个数据报已连接之后:
数据报的发送可在soket上使用write()(或者send())来完成并且会自动被发送到同样的对等socket上。与sendto一样每个write调用会发送一个独立的数据报;
在这个socket上智能读取由对等socket发送的数据报。
注意connect()的作用对数据报socket是不对称的。
上面的论断只适用于调用了connect()数据报socket,
并不适用于他链接的远程socket。
为一个数据报socket设置一个对等socket,
这种做法的优势就是在该socket上传输数据时可以使用更简单的I/O系统调用,
即无需使用指定了dest_addr和addrlen参数的sendto(),
而只需要使用write()即可。
设置一个对等socket主要对那些需要向单个对等socket(通常是某种数据报客户端)发送多个数据报的应用程序是比较有用的
56.7 总结
原文链接:https://blog.csdn.net/u010783439/article/details/130189171
tcp客户端:
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc, char argv[])
{
//创建流式套接字
int s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd < 0)
perror(“”);
//连接服务器
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(8080);//服务器的端口
ser_addr.sin_addr.s_addr = inet_addr(“192.168.1.12”);//服务器的ip
int ret = connect(s_fd, (struct sockaddr)&ser_addr,sizeof(ser_addr));
if(ret<0)
perror(“”);
//收发数据
while(1)
{
char buf[128]=“”;
char r_buf[128]=“”;
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;//最后一个字符置零
write(s_fd,buf,strlen(buf));
int cont = read(s_fd,r_buf,sizeof(r_buf));
if(cont == 0)
{
printf(“server close\n”);
break;//对方关闭!!
}
printf(“recv server = %s\n”,r_buf);
}
//关闭套接字
close(s_fd);
return 0;
}
tcp服务器端1:
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
int main(int argc, char argv[])
{
//创建套接字
int lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd < 0)
perror(“”);
//绑定
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(9999);
myaddr.sin_addr.s_addr = 0;//本机所有的ip都绑定
bind(lfd,(struct sockaddr)&myaddr,sizeof(myaddr));//绑定
//监听
listen(lfd,128);
//提取
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr); // 客户端地址信息
int cfd = accept(lfd,(struct sockaddr *)&cliaddr,&len);//从已连接队列提取新的连接,并创建一个新的已连接套接字
if(cfd < 0 )
perror(“”);
char ip[16]=“”;
printf(“client ip=%s port=%d\n”,inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),ntohs(cliaddr.sin_port));
//读写
while(1)
{
char buf[256]=“”;
//回射客户器
int ret = read(cfd,buf,sizeof(buf));//阻塞
if(ret == 0)
{
printf(“client close\n”);
break;
}
printf(“recv = [%s]\n”,buf);
write(cfd,buf,ret);
}
//关闭
close(lfd);
close(cfd);
return 0;
}
tcp多进程实现并发服务器端2:
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc, char *argv[])
{
//创建流式套接字
int lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd < 0)
perror(“”);
//连接服务器
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(8080);//服务器的端口
myaddr.sin_addr.s_addr = 0;
bind(lfd, (struct sockaddr *)&myaddr, sizeof(myaddr));
listen(lfd, 128);
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
while(1){
int cfd = accept(lfd, (struct sockaddr*)& cliaddr, &len );
if(cfd < 0)
perror("");
char ip[16] = "";
printf("client ip = %s port = %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16),
ntohs(cliaddr.sin_port));
pid_t pid;
pid = fork();
if(pid == 0){
close(lfd);
while(1){
char buf[256] = "";
int n = read(cfd, buf, sizeof(buf));
if(n == 0){
printf("client close\n");
break;
}
printf("%s\n", buf);
write(cfd, buf, n);
}
break;
}else if(pid > 0){
close(cfd);
}
}
//关闭套接字
return 0;
}
tcp信号回收子进程服务器端3:
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <strings.h>
//信号回收子进程
//信号的包裹处理 有问题的 待日后修改20240615
void perr_exit(const char * s){
perror(s);
exit(-1);
}
int Accept(int fd, struct sockaddr * sa, socklen_t * salenptr){
int n;
again:
if((n = accept(fd, sa, salenptr)) < 0){//如果是被信号中断和软件层次中断,不能退出
if((errno == ECONNABORTED) || (errno == EINTR))
goto again;## 标题
else
perr_exit(“accept error”);
}
return n;
}
int Bind(int fd, const struct sockaddr* sa, socklen_t salen){
int n;
if((n = bind(fd, sa, salen)) < 0)
perr_exit(“bind error”);
return 0;
}
void cath_child(int num){
pid_t pid;
while(1){
pid = waitpid(-1, NULL, WNOHANG);
if(pid <= 0){//没有回收到, 或还在运行,就跳出
break;
}else if(pid > 0){//回收,就继续
printf("child process %d\n", pid);
continue;
}
}
}
int main(int argc, char *argv[])
{
//sigchld 加入到阻塞集
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK,&set, NULL);
//创建流式套接字
int lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd < 0)
perror(“”);
//连接服务器
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(8080);//服务器的端口
myaddr.sin_addr.s_addr = 0;
bind(lfd, (struct sockaddr *)&myaddr, sizeof(myaddr));
listen(lfd, 128);
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
while(1){
int cfd = accept(lfd, (struct sockaddr*)& cliaddr, &len );
if(cfd < 0)
perror("");
char ip[16] = "";
printf("client ip = %s port = %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16),
ntohs(cliaddr.sin_port));
pid_t pid;
pid = fork();
if(pid == 0){
close(lfd);
while(1){
char buf[256] = "";
int n = read(cfd, buf, sizeof(buf));
if(n == 0){
printf("client close\n");
break;
}
printf("********%s*********\n", buf);
write(cfd, buf, n);
}
break;
}else if(pid > 0){
close(cfd);
struct sigaction act;
act.sa_handler = cath_child;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD, &act, NULL );
sigprocmask(SIG_UNBLOCK,&set, NULL);
}
}
//关闭套接字
return 0;
}