第七章 网络编程

在这里插入代码片# 第七章 网络编程

7.1 基本网络常识

7.2 socket

7.2.1socket编程

7.2.1.1 套接字(Socket)和Socket API简介

套接字是一种通讯机制,是一种使用标准Unix/Linux文件描述字与其他主机进程通讯的手段。从程序员的角度来看,套接字很像文件描述字(一个整数),因为它同文件和管道一样可以用read() / write()来读写数据。但是,套接字与普通文件描述字不同。

首先,套接字不像文件描述符那么简单,它除了需要地址端口等信息之外,还明确包含有关于通信的其他属性(如地址族,协议族)。

其次,套接字的使用可以是非对称的,它通常明确的区分通信的两个进程为客户进程和服务进程,并且允许不同系统或主机上的多个客户与单个服务进程相连。

最后,套接字的创建以及控制套接字的各种操作与文件描述字也有所不同,这些操作的不同是由于建立套接字网络比磁盘访问要复杂而带来的。

进程可以通过Socket API来实现对套接字进行操作,从而实现访问网络,Socket API是最初作为TCP/IP协议代码的。Socket API在其他操作系统中得到广泛的支持,包括众多的类Unix操作系统(AIX,MAC,Linux等)和Windows系统。

Socket层在内核中实现,如果应用程序想要调用它就要通过系统调用方式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G8bklXRF-1572697158129)(第七章 网络编程.assets/1565923071193.png)]

套接字是全双工通信,管道是半双工通信。

7.2.1.2 网络编程基础知识
服务器与客户端模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3s95AfBu-1572697158130)(第七章 网络编程.assets/1565923144312.png)]

服务器
  • 服务进程通过系统调用socket() 创建一个套接字。
  • 服务进程要给该套接字绑定一个端口,通过bind()来实现。
  • 服务进程调用listen()等待客户进程与该套接字连接。因为服务器必须允许多个客户同时与该套接字连接,所以listen()函数用于为客户端连接创建一个连接队列,调用 accept()接受这些连接。
  • 服务进程每调用一次accept()便创建一个新的套接字,不同与socket()创建的套接字,这个新的套接字完全只用于特定的客户通信,而socket()创建的套接字则保留用于等待其他客户链接的到来。
  • 使用accept()创建的套接字与客户端进行读写操作。
  • 使用close()shutdown()来断开连接,释放资源。
客户端
  • 客户进程通过系统调用socket()创建一个套接字。
  • 然后通过ip地址以及端口作为参数,调用connect()与服务器建立连接。
  • 使用socket()创建的套接字与服务器进行读写操作。
  • 使用close()shutdown()来关闭套接字,释放资源。
7.2.1.3 TCP连接与断开

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pa9Y0TwI-1572697158130)(第七章 网络编程.assets/1565923313531.png)]

客户端主动发起请求,计算机A是客户端,计算机B是服务器端。

三次握手是在connect和accept函数之间建立的。客户端从connect返回说明三次握手成功,服务器端从accept返回说明三次握手成功。

TCP是面向连接的可靠的流协议。

TCP不同的控制报文的作用:
  • SYN报文:建立连接的请求。

  • FIN报文:用来关闭连接的请求。

  • ACK报文:用来确认数据。

TCP建立一次连接需要经历三次握手过程
  • 服务器端做好监听准备,通常是调用 socket() 、 bind() 、和 listen() 函数监听服务器的某个已知端口。

  • 客户端通过调用 socket() 和 connect() 函数,导致客户端TCP层发送一个SYN报文请求连接,以及初始序号(例如 I )。

  • 服务器接收到SYN报文后,发送包含服务器初始序号(例如 J )的SYN报文段作为应答,同时回复ACK报文确认客户端发出的SYN,确认序号设置为 I+1 。

  • 客户端接收到SYN报文后,回复ACK报文确认服务器发出的SYN,确认序号设置为 J+1

TCP连接的关闭过程
  • A主机发送一个FIN终止这个方向连接,序号为 M ,通常是调用 close() 导致。
  • B主机收到FIN,响应一个ACK,ACK确认序号设置为 M+1 ,同时必须通知应用程序对方已经终止了连接,通常是对I/O操作函数返回文件结束标志(通过read()、write()察觉到这个结束标志)。
  • B主机也调用 close() , 发送一个FIN给A主机,序号为 N 。
  • A主机收到FIN后将响应一个ACK,序号为 N+1。
7.2.1.4 字节序

不同种类的计算机在存储“字”数据是,使用不同的字节序协定。

大端字节序: 是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。

小端字节序: 是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。

x86框架采用小端字节序,ARM架构启动时可以设置大小端字节序。

互联网是个复杂的结合体,由各式各样的主机构成,为了规范字节序,网络字节序采用大端字节序。

函数原型-htonl、htons、ntohl、noths
#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

字节序的转换

h表示host,n表示network,l表示32位长整数,s表示16位短整数。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

7.2.1.5 地址族

一般而言,在使用套接字通信之前,我们首先要为套接字选定一个地址族,简单的说,为套接字指定地址族的目的是告诉套接字使用哪一种网络层进行通讯(IPv4 或 IPv6)。

在Linux系统中,sys/socket.h头文件定义了繁多的地址族类型,它们都“AF_”或"IPv6"。

AF_INET:使用TCP或UDP来传输,用IPv4的地址。AF是address family的缩写

AF_INET6:使用TCP或UDP来传输,用IPv6的地址。

AF_UNIX:本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台机器上的时候使用。用于本地通信,是进程间通信的一种方式。

socket不仅可以用于网络间进程通信,还可以用于本地进程间通信。

7.2.1.6 地址结构

struct sockaddr 很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in(IPv4)还是sockaddr_in6(IPv6),由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EGdQlhZj-1572697158130)(第七章 网络编程.assets/1565923716771.png)]

108字节路径名:是一个char*,可以保存107个字节的路径。

struct sockaddr_in和struct sockaddr_un需要强转成struct sockaddr,在使用前两种结构体的时候,不能按照struct sockaddr去解释,需要根据前两个字节的信息,转成相应的结构体。

sockaddr
#include <sys/socket.h>

struct sockaddr 
{
    sa_family_t sa_family; //地址族 
    char sa_data[14];      //地址值,实际可能更长。    
};
IPV4
#include <sys/socket.h>

struct sockaddr_in 
{
    __kernel_sa_family_t sin_family; //AF_INET4 
    __be16 sin_port;                 //端头号   
    struct in_addr sin_addr;         //IPv4 地址
    
    // 8字节填充   
    unsigned char __pad[__SOCK_SIZE__ ‐ sizeof(short int)sizeof(unsigned short int)sizeof(struct in_addr)];    
};

struct in_addr 
{
	__be32 s_addr;    
};

be是big endian的缩写

IPV6
struct sockaddr_in6 
{
    unsigned short int sin6_family; //AF_INET6    
    __be16 sin6_port;               //端口号   
    __be32 sin6_flowinfo;           //IPv6 flow information
    struct in6_addr sin6_addr;      //IPv6 地址 128bit  
    __u32 sin6_scope_id;            //scope id (new in RFC2553)
};

struct in6_addr 
{
    union 
    {
        __u8 u6_addr8[16];    
        __be16 u6_addr16[8];    
        __be32 u6_addr32[4];    
    } in6_u;
    
    #define s6_addr in6_u.u6_addr8
    #define s6_addr16 in6_u.u6_addr16
    #define s6_addr32 in6_u.u6_addr32
};
本地socket通信
#define UNIX_PATH_MAX 108

struct sockaddr_un 
{
    __kernel_sa_family_t sun_family; //AF_UNIX  
    char sun_path[UNIX_PATH_MAX];    //pathname    
};
7.2.1.7 IP转换

ipv4以及的ipv6点分十进制表达法与二进制整数之间的互转。

函数原型-inet_pton
#include <arpa/inet.h>

int inet_pton(int af, const char *src, void *dst);

inet_pton 把字符串 src 转换成ip地址保存在 dst 中。该函数调用成功返回大于0的整数。

af 用来指定要转换的ip属于什么协议族。

函数原型-inet_ntop
#include <arpa/inet.h>

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

inet_ntop() 把网络字节序的ip地址 src 转换成字符串保存在 dst 中作为返回值返回,参数 size 为 dst 所包含的字节数。

示例-IP地址的转换
#include <stdio.h>
#include <arpa/inet.h>

int main()
{
	char ip_str[] = "192.168.1.111";

	unsigned int ip_i;
	inet_pton(AF_INET,ip_str,&ip_i);
	printf("inet_pton: IP %s [%x]\n",ip_str,ip_i);

	char buf[16];
	inet_ntop(AF_INET,&ip_i,buf,sizeof(buf));
	printf("inet_ntop: IP %s\n",buf);

	return 0;
}

inet_pton: IP 192.168.1.111 [6f01a8c0]
inet_ntop: IP 192.168.1.111


需要使用unsigned int的大端序来保存ip地址

7.2.2 socket API

7.2.2.1 创建套接字
函数原型-socket
#include <sys/types.h> 
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
参数
domain

AF_INET:这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址。
AF_INET6:与上面类似,不过是来用IPv6的地址。
AF_UNIX:本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用。

type

SOCK_STREAM:这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的 socket类型,这个socket是使用TCP来进行传输。
SOCK_DGRAM: 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。

protocol

0 默认协议, 一般填默认的。

返回值

成功返回一个新的套接字,失败返回-­1,设置errno。

7.2.2.2 给本地套接字赋予地址和端口
函数原型-bind
#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

bind()的作用是将参数sockfd和addr绑定在一起。

参数
sockfd

socket套接字

addr

填写IP地址、端口号、协议族等信息。

addrlen

addr结构体实例的大小

返回值

成功返回0,失败返回­1, 设置errno

使用方法
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(8000);

首先将整个结构体清零,然后设置地址类型为AF_INET,网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址,端口号为8000。

7.2.2.3 给连接排队

当我们给服务器端套接调用bind()指定了本地IP地址后,这就如同电话总机已经有了具体的号码,而端口号就如同我们的分机号码,此时需要把分机号码的振铃打开,同时还要对同时拨入的多个电话进行排队。

函数原型-listen
#include <sys/socket.h>

int listen(int sockfd, int backlog);
参数
sockfd

参数为成功调用socket函数返回的套接字,并已经成功调用了bind()。

backlog

参数告诉套接字在忙于处理上一个请求时,还可以接受多少个进入的请求,换句话说,这决定了挂起连接的队列的大小。

服务器在与客户端建立连接的时候(三次握手的时候),允许排队等待连接socket的数量。

查看系统默认backlog

cat /proc/sys/net/ipv4/tcp_max_syn_backlog    #128

不能及时处理客户端的请求时,最多能缓存128个请求。

listen函数不会是进程阻塞,只是告诉系统给它创建一个这样的队列。

返回值

调用成功返回0,失败返回­-1,设置errno。

7.2.2.4 接受网络连接

accept() 调用类似于听见电话铃声后接起电话,此时,你已经建立起一个与你的客户的连接,这个连接保存直到你或你的客户挂线,网络服务器端也有类似的过程,它使用 accept() 函数接受客户端的连接。

函数原型-accept
#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
sockdf

参数为成功调用socket函数返回的套接字,并已经成功调用了 bind() 和 listen() 。

addr

传出参数,返回连接客户端地址信息,含IP地址和端口号。(如果使用IPv4地址族,那么它需要我们填个指向sockaddr_in结构的指针)

addrlen

传入传出参数(值­结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小。

返回值

成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-­1,设置errno。

使用方法

监听套接字数据传输套接字

监听套接字用于建立套接字连接,一旦连接成功,则返回数据传输套接字用于数据传输。

主程序会阻塞在accept上,用于建立链接。listen相当于安装好电话,且给电话设置了一个排队的队列。accept相当于守着电话,接听电话,接通以后就转出去。当在和其中一个客户建立链接的时候,又有其他客户请求链接,则其他客户会在请求队列中排队。

7.2.2.5 连接远程主机

请求连接是客户端的动作,TCP/IP客户端需要调用 connect() 去连接一个服务器。

函数原型-connect
#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
sockdf

参数是成功调用socket()返回的套接字。

addr

传入参数,指定服务器端地址信息,含IP地址和端口号。

addrlen

传入参数,如IPV4传入sizeof(struct sockaddr_in)大小。

返回值

如果调用成功,表示连接已经建立(三次握手完成),返回0。否则返回-1并将错误码代存放于全局变量errno之中。

7.2.2.6 Socket I/O

对于套接字的I/O(读写)有很多API可以用,如send()、recv()、readv()、writev()、sendmsg()、recvmsg()、read()、write()等等。这里我们只介绍常用的I/O接口read()和write()。

#include <unistd.h>

int read(int sockfs, void *buf, size_t nbytes);
int write(int sockfd, void *buf, size_t nbytes);
7.2.2.7 关闭套接字

关闭套接字可以简单的调用close()函数

函数原型-close
#include <unistd.h>

int close(int sockfd);

调用close()会触发四次握手关闭连接。

7.2.2.8 搭建一个TCP服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TJ4IZOAN-1572697158130)(第七章 网络编程.assets/TCP服务器.png)]

#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main()
{
	char buf[MAX_BYTES];

	//初始化服务的地址结构体
	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//本服务器上的任意一个IP

	//创建一条用于监听的套接字
	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	//绑定IP和端口
	//IP绑定不会失败,但端口号可能已被使用,可能会出问题。
	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	//让套接字有监听的能力
	//在fd没有绑定IP和端口的时候回失败。
	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		//接收客链接请求户端的TCP
		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}

begin accept
accept from IP : 127.0.0.1 Port : 58444
begin accept
accept from IP : 192.168.40.138 Port : 55626
begin accept
accept from IP : 192.168.40.138 Port : 55628
begin accept


通过本地命令访问服务器nc 127.0.0.1 8888nc 192.168.40.138 8888nc 192.168.40.138 8888

客户端的端口号是系统分配的,每次访问服务器端口号是不固定的。

绑定服务器的IP地址为0.0.0.0,目的是不指名连接服务器上某个具体的网卡,只要能ping通服务器的任何网卡,都可以建立链接。

socket用于建立电话线,bind申请电话号码,listen配置电话机和请求队列,accept接听电话并转接。

7.2.2.9 搭建一个TCP客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.138"

int main()
{
	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	return 0;
}

connect success…


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6LTsv5VP-1572697158131)(第七章 网络编程.assets/TCP的建立与释放.png)]

TCP建立与释放

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7RFALn8R-1572697158132)(第七章 网络编程.assets/服务端没有打开端口.png)]

链接一个没有打开的服务器,connect函数会报connect: Connection refused错误。但在服务端可以捕获TCP的报文。

关闭服务端的网卡,则无法建立链接,且服务端收不到报文。客户端会报connect:Network is unreachable错误。

7.2.2.10服务端与客户端通信
示例-客户端给服务端发送数据
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MAX_BYTES 4096

int main()
{
	char buf[MAX_BYTES];
    
	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		int ret = read(client_fd,buf,sizeof(buf));
		if(ret == -1)
		{
			perror("read");
			close(client_fd);
		}
		else
		{
			buf[ret] = '\0';
			printf("ret = %d,buf %s\n",ret,buf);
			close(client_fd);
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.139"

int main()
{
	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	ret = write(socket_fd,"helloworld",10);
	if(ret == -1)
	{
		perror("write");
	}
	else
	{
		printf("write %d bytes\n",ret);
	}

	close(socket_fd);

	return 0;
}

connect success…
write 10 bytes


begin accept
accept from IP : 192.168.40.135 Port : 39792
ret = 10,buf helloworld
begin accept


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xrhqf7Zz-1572697158132)(第七章 网络编程.assets/客户端和服务端发生数据.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ceOyROFa-1572697158132)(第七章 网络编程.assets/1566231376558.png)]

客户端发给服务端的push包中有data。有应用层的数据了。

当客户端没有写入数据时,服务器会阻塞在read函数中,直到客户端写入数据。但可以通过fcntl函数把socket修改成非阻塞的。

示例-客户端连续写两次,服务器读一次。
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MAX_BYTES 4096

int main()
{
	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		int ret = read(client_fd,buf,sizeof(buf));
		if(ret == -1)
		{
			perror("read");
			close(client_fd);
		}
		else
		{
			buf[ret] = '\0';
			printf("ret = %d,buf %s\n",ret,buf);
			close(client_fd);
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.139"

int main()
{
	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	ret = write(socket_fd,"helloworld",10);
	if(ret == -1)
	{
		perror("write");
	}
	else
	{
		printf("write %d bytes\n",ret);
	}

	ret = write(socket_fd,"1234567890",10);
	if(ret == -1)
	{
		perror("write");
	}
	else
	{
		printf("write %d bytes\n",ret);
	}

	close(socket_fd);

	return 0;
}

connect success…
write 10 bytes
write 10 bytes


begin accept
accept from IP : 192.168.40.135 Port : 39812
ret = 20,buf helloworld1234567890
begin accept


客户端写两次,服务端读一次。

TCP是流协议,UDP是数据报协议,数据报协议每次读大小是固定的。

示例-客户端和服务器连续通信
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MAX_BYTES 4096

int main()
{
	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		while(1)
		{
			int ret = read(client_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				close(client_fd);
			}
			else
			{
				buf[ret] = '\0';
				printf("ret = %d,buf %s\n",ret,buf);
			}
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.139"

int main()
{
	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	int count = 0;
	char buf[1024];

	while(1)
	{
		sprintf(buf,"DATA %d",count++);

		ret = write(socket_fd,buf,strlen(buf));
		if(ret == -1)
		{
			perror("write");
		}
		else
		{
			printf("write %d bytes\n",ret);
		}

		sleep(1);
	}
	
	close(socket_fd);

	return 0;
}

connect success…
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes


ret = 6,buf DATA 0
ret = 6,buf DATA 1
ret = 6,buf DATA 2
ret = 6,buf DATA 3
ret = 6,buf DATA 4
ret = 6,buf DATA 5
ret = 6,buf DATA 6


客户端每一秒钟写一次,服务端在没有数据读的时候阻塞在read函数上。

示例-客户端关闭,服务器读到0字节。
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MAX_BYTES 4096

int main()
{
	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		while(1)
		{
			int ret = read(client_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				close(client_fd);
			}
			else
			{
				buf[ret] = '\0';
				printf("ret = %d,buf %s\n",ret,buf);
			}
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.139"

int main()
{
	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	int count = 0;
	char buf[1024];

	while(1)
	{
		sprintf(buf,"DATA %d",count++);

		ret = write(socket_fd,buf,strlen(buf));
		if(ret == -1)
		{
			perror("write");
		}
		else
		{
			printf("write %d bytes\n",ret);
		}

		sleep(1);
	}
	
	close(socket_fd);

	return 0;
}

connect success…
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
^C


ret = 6,buf DATA 0
ret = 6,buf DATA 1
ret = 6,buf DATA 2
ret = 6,buf DATA 3
ret = 6,buf DATA 4
ret = 0,buf
ret = 0,buf
ret = 0,buf
ret = 0,buf
ret = 0,buf
ret = 0,buf
ret = 0,buf
^C


客户端关闭,服务端read函数循环读出0字节。

示例-服务器读端发现客户端写端关闭以后的处理
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MAX_BYTES 4096

int main()
{
	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		while(1)
		{
			int ret = read(client_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				close(client_fd);
				break;
			}
			else if(ret == 0)   //发现写端关闭以后,关闭读端。
			{
				printf("client has close\n");
				close(client_fd);
				break;
			}
			else
			{
				buf[ret] = '\0';
				printf("ret = %d,buf %s\n",ret,buf);
			}
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.139"

int main()
{
	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	int count = 0;
	char buf[1024];

	while(1)
	{
		sprintf(buf,"DATA %d",count++);

		ret = write(socket_fd,buf,strlen(buf));
		if(ret == -1)
		{
			perror("write");
		}
		else
		{
			printf("write %d bytes\n",ret);
		}

		sleep(1);
	}
	
	close(socket_fd);

	return 0;
}

connect success…
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
^C


begin accept
accept from IP : 192.168.40.135 Port : 39830
ret = 6,buf DATA 0
ret = 6,buf DATA 1
ret = 6,buf DATA 2
ret = 6,buf DATA 3
client has close
begin accept


读出零字节说明对方关闭了,接下来就可以把自己关闭了。

示例-两个客户端和一个服务器同时通信
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MAX_BYTES 4096

int main()
{
	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		while(1)
		{
			int ret = read(client_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				close(client_fd);
				break;
			}
			else if(ret == 0)   //发现写端关闭以后,关闭读端。
			{
				printf("client has close\n");
				close(client_fd);
				break;
			}
			else
			{
				buf[ret] = '\0';
				printf("ret = %d,buf %s\n",ret,buf);
			}
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.139"

int main()
{
	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	int count = 0;
	char buf[1024];

	while(1)
	{
		sprintf(buf,"DATA %d",count++);

		ret = write(socket_fd,buf,strlen(buf));
		if(ret == -1)
		{
			perror("write");
		}
		else
		{
			printf("write %d bytes\n",ret);
		}

		sleep(1);
	}
	
	close(socket_fd);

	return 0;
}

connect success…
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 7 bytes
^C


connect success…
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
^C


begin accept
accept from IP : 192.168.40.135 Port : 39842
ret = 6,buf DATA 0
ret = 6,buf DATA 1
ret = 6,buf DATA 2
ret = 6,buf DATA 3
ret = 6,buf DATA 4
ret = 6,buf DATA 5
ret = 6,buf DATA 6
ret = 6,buf DATA 7
ret = 6,buf DATA 8
ret = 6,buf DATA 9
ret = 7,buf DATA 10
client has close
begin accept
accept from IP : 192.168.40.135 Port : 39844
ret = 24,buf DATA 0DATA 1DATA 2DATA 3
ret = 6,buf DATA 4
ret = 6,buf DATA 5
client has close
begin accept


两个客户端和服务端连接时,此时的服务端只能处理一个客户端,只有先连接的客户端退出以后,服务端从能从内层的while循环退出,来到外层循环继续处理另外一个客户的请求。会被系统缓存的客户端请求一并读出。

示例-服务器读端关闭,客户端收到SIGPIPE信号。
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MAX_BYTES 4096

int main()
{
	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		while(1)
		{
			int ret = read(client_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				close(client_fd);
				break;
			}
			else if(ret == 0)
			{
				printf("client has close\n");
				close(client_fd);
				break;
			}
			else
			{
				buf[ret] = '\0';
				printf("ret = %d,buf %s\n",ret,buf);
			}
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.139"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	int i = 1;
	for(;i<32;i++)
		signal(i,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	int count = 0;
	char buf[1024];

	while(1)
	{
		sprintf(buf,"DATA %d",count++);

		ret = write(socket_fd,buf,strlen(buf));
		if(ret == -1)
		{
			perror("write");	//出错以后继续
		}
		else
		{
			printf("write %d bytes\n",ret);
		}

		sleep(1);
	}
	
	close(socket_fd);

	return 0;
}

begin accept
accept from IP : 192.168.40.135 Port : 39846
ret = 6,buf DATA 0
ret = 6,buf DATA 1
ret = 6,buf DATA 2
ret = 6,buf DATA 3
^C


connect success…
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
signal 13
write: Broken pipe


服务端关闭,则客户端会退出。通过注册所有信号函数,获取客户端是被什么信号杀死的。

服务端关闭以后,客户端再写一次就会关闭。客户端往读端关闭的socket中写入数据,会被信号13杀死。

服务端关闭以后,会给客户端发送FIN,客户端回复ACK,客户端在PSH数据,服务端回复RST,客户端的端口号被重置掉了,客户端再PSH数据,客户端将会收到SIGPIPE信号。

如果socket读端关闭,写端继续写,第一次能写,第二次将会收到SIGPIPE信号,并且write函数返回-1,errno设置为EPIPE。

第一次写会收到一个RST包,第二次会收到一个SIGPIPE信号。write返回-1,errno值为EPIPE。

示例-客户端写端发现服务器读端关闭以后的处理
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MAX_BYTES 4096

int main()
{
	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		while(1)
		{
			int ret = read(client_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				close(client_fd);
				break;
			}
			else if(ret == 0)
			{
				printf("client has close\n");
				close(client_fd);
				break;
			}
			else
			{
				buf[ret] = '\0';
				printf("ret = %d,buf %s\n",ret,buf);
			}
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.139"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	signal(SIGPIPE,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	int count = 0;
	char buf[1024];

	while(1)
	{
		sprintf(buf,"DATA %d",count++);

		ret = write(socket_fd,buf,strlen(buf));
		if(ret == -1)
		{
			perror("write");
             close(socket_fd);
			exit(1);   //出错以后会退出
		}
		else
		{
			printf("write %d bytes\n",ret);
		}

		sleep(1);
	}
	
	close(socket_fd);

	return 0;
}

begin accept
accept from IP : 192.168.40.135 Port : 39848
ret = 6,buf DATA 0
ret = 6,buf DATA 1
ret = 6,buf DATA 2
^C


connect success…
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
signal 13
write: Broken pipe


客户端处理服务端关闭的情况,注册SIGPIPE信号函数,处理收到这个信号时的操作。

EPIPE fd is connected to a pipe or socket whose reading end is closed.When this happens the writing process will also receive a SIGPIPE signal.(Thus,the write return value is seen only if the program catches, blocks or ignores this signal.)

客户端处理收到的SIGPIPE信号,关闭socket。然后再次创建socket进行链接,如果链接三次都是失败的,则可以认为服务器挂了。

示例-服务器在读阻塞时被信号打断
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = 0;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGINT,&sigact,NULL);

	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		while(1)
		{
			int ret = read(client_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				close(client_fd);
				break;
			}
			else if(ret == 0)
			{
				printf("client has close\n");
				close(client_fd);
				break;
			}
			else
			{
				buf[ret] = '\0';
				printf("ret = %d,buf %s\n",ret,buf);
			}
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.139"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	signal(SIGPIPE,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	int count = 0;
	char buf[1024];

	while(1)
	{
		sprintf(buf,"DATA %d",count++);

		ret = write(socket_fd,buf,strlen(buf));
		if(ret == -1)
		{
			perror("write");
			exit(1);
		}
		else
		{
			printf("write %d bytes\n",ret);
		}

		sleep(1);
	}
	
	close(socket_fd);

	return 0;
}

connect success…
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
signal 13
write: Broken pipe


begin accept
accept from IP : 192.168.40.135 Port : 39852
ret = 6,buf DATA 0
ret = 6,buf DATA 1
ret = 6,buf DATA 2
ret = 6,buf DATA 3
^Csignal 2
read: Interrupted system call
begin accept


服务端在读阻塞时被信号打断,打断就退出了。

示例-服务器读阻塞被信号打断之后重启read函数
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = 0;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGINT,&sigact,NULL);


	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		while(1)
		{
			int ret = read(client_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");

				if(errno == EINTR)
					continue;
				else
				{
					close(client_fd);
					break;
				}
			}
			else if(ret == 0)
			{
				printf("client has close\n");
				close(client_fd);
				break;
			}
			else
			{
				buf[ret] = '\0';
				printf("ret = %d,buf %s\n",ret,buf);
			}
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.139"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	signal(SIGPIPE,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	int count = 0;
	char buf[1024];

	while(1)
	{
		sprintf(buf,"DATA %d",count++);

		ret = write(socket_fd,buf,strlen(buf));
		if(ret == -1)
		{
			perror("write");
			exit(1);
		}
		else
		{
			printf("write %d bytes\n",ret);
		}

		sleep(1);
	}
	
	close(socket_fd);

	return 0;
}

connect success…
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 6 bytes
write 7 bytes
write 7 bytes
signal 13
write: Broken pipe


begin accept
accept from IP : 192.168.40.135 Port : 39854
ret = 6,buf DATA 0
ret = 6,buf DATA 1
ret = 6,buf DATA 2
ret = 6,buf DATA 3
^Csignal 2
read: Interrupted system call
ret = 6,buf DATA 4
ret = 6,buf DATA 5
ret = 6,buf DATA 6
ret = 6,buf DATA 7
^Csignal 2
read: Interrupted system call
ret = 6,buf DATA 8
ret = 6,buf DATA 9
ret = 7,buf DATA 10
^\Quit (core dumped)


服务端read函数阻塞时被信号打断,信号打断不是重要错误,可以重启read函数继续读。

示例-服务器阻塞在accpet函数时,被信号打断。
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = 0;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGINT,&sigact,NULL);


	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		while(1)
		{
			int ret = read(client_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");

				if(errno == EINTR)
					continue;
				else
				{
					close(client_fd);
					break;
				}
			}
			else if(ret == 0)
			{
				printf("client has close\n");
				close(client_fd);
				break;
			}
			else
			{
				buf[ret] = '\0';
				printf("ret = %d,buf %s\n",ret,buf);
			}
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}

begin accept
^Csignal 2
accept: Interrupted system call
accept from IP : 0.0.0.0 Port : 0
read: Bad file descriptor
begin accept
^\Quit (core dumped)


没有处理accept时被信号打断。

示例-服务器阻塞在accept函数上被信号打断后重启
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = 0;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGINT,&sigact,NULL);


	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
			continue;  //阻塞在accept函数上时,被信号打断。
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		while(1)
		{
			int ret = read(client_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");

				if(errno == EINTR)
					continue;
				else
				{
					close(client_fd);
					break;
				}
			}
			else if(ret == 0)
			{
				printf("client has close\n");
				close(client_fd);
				break;
			}
			else
			{
				buf[ret] = '\0';
				printf("ret = %d,buf %s\n",ret,buf);
			}
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}

begin accept
^Csignal 2
accept: Interrupted system call
begin accept
^Csignal 2
accept: Interrupted system call
begin accept
^\Quit (core dumped)


处理过以后的运行结果

阻塞在accept函数中被信号打断时,不要让程序往下执行,因为返回socket是错误的,需要continue再次执行到accept函数。


begin accept
^Csignal 2
accept: Interrupted system call
begin accept
^Csignal 2
accept: Interrupted system call
begin accept
accept from IP : 192.168.40.135 Port : 39856
ret = 6,buf DATA 0
ret = 6,buf DATA 1
ret = 6,buf DATA 2
ret = 6,buf DATA 3
ret = 6,buf DATA 4
ret = 6,buf DATA 5
ret = 6,buf DATA 6
ret = 6,buf DATA 7
ret = 6,buf DATA 8
ret = 6,buf DATA 9
^\Quit (core dumped)


被处理以后的服务端也是可以正常使用的

示例-服务器阻塞在函数上时,被信号打断后,信号重启被打断的函数。
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = SA_RESTART;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGINT,&sigact,NULL);

	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		while(1)
		{
			int ret = read(client_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				close(client_fd);
				break;
			}
			else if(ret == 0)
			{
				printf("client has close\n");
				close(client_fd);
				break;
			}
			else
			{
				buf[ret] = '\0';
				printf("ret = %d,buf %s\n",ret,buf);
			}
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}

begin accept
^Csignal 2
accept from IP : 192.168.40.135 Port : 39858
ret = 6,buf DATA 0
ret = 6,buf DATA 1
ret = 6,buf DATA 2
ret = 6,buf DATA 3
^Csignal 2
ret = 6,buf DATA 4
ret = 6,buf DATA 5
ret = 6,buf DATA 6
ret = 6,buf DATA 7
^\Quit (core dumped)


如果在信号中设置了sigact.sa_flags=SA_RESTART,则被信号打断的函数可以再次重启。被打断的函数不会返回错误值,被打断的函数就像没有发生信号事件一样。

服务器和客户端通信小结
  • 当socket写端关闭的时候,读端read将一直读取到0字节,可以使用这一特性来关闭读端套接字。

  • 当socket读端关闭的时候,写端write继续写入数据,将会报错,并且收到一个SIGPIPE信号。

  • 当网络异常的时候(如拔掉网线),这个时候read不会返回错误,将阻塞等待数据;write也不会返回错误,仍然可以写入数据。

  • socket在有个buffer,并且这个buffer的大小是一定的,当一直往socket write的时候,达到这个buffer的最大值后,write将阻塞。

socket有个写buffer,如果写buffer写满,那么write将会阻塞。

客户端的网络断开时,服务端的read函数将会阻塞,而客户端的write可以一直写到socket的buffer满为止,然后阻塞。

客户端断开的网络再次可以连接时,存在客户端socket的buffer中的数据可以全发给服务端,服务端可以读到断网时,客户端写入buffer中的内容。

服务端的网络断开时,客户端一直重复发送同一条内容,因为这条内容没有收到ACK。发送一段时间就停止了,过一段时间再去发送。但write函数是一直可以写的,只是主机不再往外发送数据了。

网络断开,write函数依然可以往socket的buffer中写入数据,直到写满才会阻塞。此时的主机只会发送最近没有发送出去的TCP数据报,主机尝试着发送一些TCP后,依然没有收到回复时,主机就过一段时间再次去尝试。

当网络恢复以后,主机会把buffer中的内容发送至目的主机。但TCP发送时有MTU的限制,TCP Segment Len:1092字节。若buffer中的数据大于1092,则将buffer中的数据分片发送。

如果网络不通,write函数可以继续写,直到写buffer被填满,read函数阻塞等待数据。如果网络恢复,通信也恢复正常,不会丢失数据。

TCP的写端速度比读端速度快,写端会写满buffer,write将会阻塞。

TCP中第一次写100字节,第二次写200字节,那么一次读300字节,因为TCP是流协议。

使用TCP协议进行数据传输时,突然断开网络,会发生什么?

读端的默认操作是阻塞,写端可以继续写,直到写buffer写满。如果网络恢复,可以继续读写,不需要重新建立连接。

UDP接收端每次只能接收一个数据报,即使客户端在极短的时间内发送了两个数据报,接收端也只能一个一个收数据报。UDP的数据不可靠体现在两点:数据丢失不重传,数据顺序可能无序。

7.3 UDP编程

7.3.1 UDP概述

TCP与UDP应用程序之间存在本质差异,UDP是无连接的、不可靠的数据报协议,而TCP是面向连接的,提供可靠的字节流。然而,有些情况下更适合用UDP而不是TCP。

在互联网中个,有很多流行的应用层协议是用UDP实现的,如DNS(域名系统)、NFS(网络文件系统)。另外UDP还提供了广播和多播的特性,这是TCP无法做到的。

下面是典型的UDP服务器客户端。客户端不与服务器建立连接,它调用函数 sendto() 给服务器发送数据报,此函数要求目的地址作为其参数。类似的,服务器不用调用 accept() 接受连接,它只管调用函数 recvfrom() , 等待来自某客户端数据到达。 recvfrom() 在读入数据的同时返回客户的协议地址,所以服务器可以根据 recvfrom() 返回的客户端地址发送应答。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6okt0QWZ-1572697158133)(第七章 网络编程.assets/1565925340423.png)]

创建一个UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

采用SOCK_DGRAM数据报模式中的默认协议,也就是UDP协议来传输。

函数原型-recvfrom
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

使用recvfrom() 发UDP数据

参数

sockfd:socket() 函数返回的套接字
buf:指定保存数据的缓冲区的指针
len:指出要读写字节数
flags:参数很少使用,一般我们会传递一个0。

返回值

如果正确接收返回接收到的字节数,失败返回-­1,并且设置errno。

函数原型-sendto
#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

使用sendto() 收UDP数据

参数

sockfd:socket() 函数返回的套接字
buf:指定保存数据的缓冲区的指针
len:指出要读写字节数
flags:参数很少使用,一般我们会传递一个0。

返回值

如果正确接收返回接收到的字节数,失败返回-­1,并且设置errno。

7.3.2 UDP服务端

#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main()
{
	int server_fd = socket(AF_INET,SOCK_DGRAM,0);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int ret = bind(server_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("bind");
		exit(errno);
	}

	char buf[4096];

	struct sockaddr_in client_addr;
	bzero(&client_addr,sizeof(client_addr));

	socklen_t socklen = sizeof(client_addr);

	while(1)
	{
		ret = recvfrom(server_fd,buf,sizeof(buf),0,(struct sockaddr*)&client_addr,&socklen);
		if(ret == -1)
		{
			perror("recvfrom");
		}
		else
		{
			buf[ret] = '\0';

			char IP_buf[INET_ADDRSTRLEN];
			inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));

			printf("recvfrom IP %s Port %d buf: %s ret %d\n",IP_buf,ntohs(client_addr.sin_port),buf,ret);
		}
	}

	close(server_fd);

	return 0;
}

7.3.3 UDP客户端

#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.169"

int main()
{
	int client_addr = socket(AF_INET,SOCK_DGRAM,0);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	char buf[4096];
	int i = 0;

	while(1)
	{
		sprintf(buf,"DATA %d",i++);

		int ret = sendto(client_addr,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
		if(ret == -1)
		{
			perror("sendto");
		}
		else
		{
			printf("send success %d\n",ret);
		}

		sleep(1);
	}

	close(client_addr);

	return 0;
}

send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 7
send success 7
send success 7
send success 7
send success 7
^C


recvfrom IP 192.168.40.140 Port 54248 buf: DATA 0 ret 6
recvfrom IP 192.168.40.140 Port 54248 buf: DATA 1 ret 6
recvfrom IP 192.168.40.140 Port 54248 buf: DATA 2 ret 6
recvfrom IP 192.168.40.140 Port 54248 buf: DATA 3 ret 6
recvfrom IP 192.168.40.140 Port 54248 buf: DATA 4 ret 6
^C
root@ubuntu:~/linux-programming/network# ./udp_server
recvfrom IP 192.168.40.140 Port 54248 buf: DATA 7 ret 6
recvfrom IP 192.168.40.140 Port 54248 buf: DATA 8 ret 6
recvfrom IP 192.168.40.140 Port 54248 buf: DATA 9 ret 6
recvfrom IP 192.168.40.140 Port 54248 buf: DATA 10 ret 7
recvfrom IP 192.168.40.140 Port 54248 buf: DATA 11 ret 7
^C


关闭写端,读端则阻塞。关闭读端,写端继续写。

发送端即使在很短的时间内发送了两个包,但接收端也是一个包一个包的接收,并不会一次性把两个包同时接收了。

流协议每次读取的内容大小是可以自己决定,而数据报协议每次读取的大小是固定的,即每个报文的大小,每次都去读一个报文的大小。

服务器关闭也不会影响发送端的发送,重启服务器会继续收到数据,中间丢失的就丢失了。

7.3.4 错误值处理

#ifndef __MYSOCKET_H__
#define __MYSOCKET_H__
#include <unistd.h>

#include <sys/types.h>
#include <arpa/inet.h>


void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
void Bind(int fd, const struct sockaddr *sa, socklen_t salen);
void Connect(int fd, const struct sockaddr *sa, socklen_t salen);
void Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
void Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
static ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/socket.h>
#include <unistd.h>
#include "mysocket.h"

void perr_exit(const char *s)
{
    perror(s);
    exit(1);
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
    int n;

again:
	//在三次握手时,没有成功。 ECONNABORTED
    if ( (n = accept(fd, sa, salenptr)) < 0)
    {
        if ((errno == ECONNABORTED) || (errno == EINTR)) 
		{ 
			goto again ;
		}
        else 
		{ 
			perr_exit("accept error"); 
		}
    }

    return n;
}

void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    if (bind(fd, sa, salen) < 0) 
	{ 
		perr_exit("bind error"); 
	}
}

void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    if (connect(fd, sa, salen) < 0) 
	{ 
		perr_exit("connect error"); 
	}
}

void Listen(int fd, int backlog)
{
    if (listen(fd, backlog) < 0) 
	{ 
		perr_exit("listen error"); 
	}
}

int Socket(int family, int type, int protocol)
{
    int n;
    if ((n = socket(family, type, protocol)) < 0) 
	{ 
		perr_exit("socket error"); 
	}

    return n;
}

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
    ssize_t n;
again:
    if ( (n = read(fd, ptr, nbytes)) ==1)
    { 
		if (errno == EINTR) 
		{ 
			goto again; 
		} 
		else 
		{ 
			return1; 
		} 
	}

    return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
    ssize_t n;
again:
    if ( (n = write(fd, ptr, nbytes)) ==1)
	{ 
		if (errno == EINTR) 
		{ 
			goto again; 
		} 
		else 
		{ 
			return1; 
		} 
	}

    return n;
}

void Close(int fd)
{
    if (close(fd) ==1) 
	{ 
		perr_exit("close error"); 
	}
}

ssize_t Readn(int fd, void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nread;
    void *ptr;
    ptr = vptr;
    nleft = n;

    while(nleft > 0)
    {
        if((nread = read(fd, ptr, nleft)) ==1)
        {
            if (errno == EINTR) 
			{ 
				continue; 
			} 
			else 
			{ 
				return1;
			}
        }
        else if(nread == 0) 
		{ 
			break; 
		}

        nleft ‐= nread;
        ptr += nread;
    }

    return n ‐ nleft;
}

ssize_t Writen(int fd, const void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    char *ptr;
    ptr = (char*)vptr;
    nleft = n;

    while (nleft > 0)
    {
        if ( (nwritten = write(fd, ptr, nleft)) ==1)
        {
            if (errno == EINTR) 
			{ 
				continue; 
			} 
			else  
			{ 
				return1; 
			}
        }
        else if(nwritten == 0) 
		{ 
			break; 
		}

        nleft ‐= nwritten;
        ptr += nwritten;
    }

    return n ‐ nleft;
}

static ssize_t my_read(int fd, char *ptr)
{
    static int read_cnt;
    static char *read_ptr;
    static char read_buf[100];
	if (read_cnt <= 0)
    {
	again:
	        if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0)
	        {
	            if (errno == EINTR) 
				{ 
					goto again; 
				}

	            return1;
	        }
	        else if (read_cnt == 0) 
			{ 
				return 0; 
			}

	        read_ptr = read_buf;
    }

    read_cnt‐‐;

    *ptr = *read_ptr++;

    return 1;
}

ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
    ssize_t n, rc;
    char c, *ptr;
    ptr = (char*)vptr;

    for (n = 1; n < maxlen; n++)
    {
        if ( (rc = my_read(fd, &c)) == 1)
        {
            *ptr++ = c;
            if (c == '\n') 
			{ 
				break; 
			}
        }
        else if (rc == 0) 
		{ 
			*ptr = 0; 

			return n ‐ 1; 
		}
        else 
		{ 
			return1; 
		}
    }

    *ptr = 0;
    return n;
}

#endif __MYSOCKET_H__

7.4 多进程服务器模型

使用多进程并发服务器时要考虑以下几点:

  • 父最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)
  • 系统内创建进程个数(内存大小相关)
  • 进程创建过多是否降低整体服务性能(进程调度)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SkaFIgpp-1572697158134)(第七章 网络编程.assets/多进程模型.png)]

示例-多进程服务器
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d\n",sig);

	while(waitpid(-1,NULL,WNOHANG) > 0);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = 0;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGCHLD,&sigact,NULL);


	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
			continue;  
		}

		pid_t pid = fork();

		if(pid == 0)
		{
			close(listen_fd);

			char IP_buf[INET_ADDRSTRLEN];
			inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
			printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

			while(1)
			{
				int ret = read(client_fd,buf,sizeof(buf));
				if(ret == -1)
				{
					perror("read");

					if(errno == EINTR)
						continue;
					else
					{
						close(client_fd);
						break;
					}
				}
				else if(ret == 0)
				{
					printf("client has close\n");
					close(client_fd);
					break;
				}
				else   
				{
					buf[ret] = '\0';
					
					printf("ret = %d,buf %s,pid = %d\n",ret,buf,getpid());
				}
			}

			close(client_fd);
			exit(0);
		}
		else if(pid == -1)
		{
			perror("fork");
			exit(0);
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.174"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	signal(SIGPIPE,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	int count = 0;
	char buf[1024];

	while(1)
	{
		sprintf(buf,"DATA %d",count++);

		ret = write(socket_fd,buf,strlen(buf));
		if(ret == -1)
		{
			perror("write");
			exit(1);
		}
		else
		{
			printf("write %d bytes,pid = %d\n",ret,getpid());
		}

		sleep(1);
	}
	
	close(socket_fd);

	return 0;
}

begin accept
begin accept
accept from IP : 192.168.40.173 Port : 44988
ret = 6,buf DATA 0,pid = 42223
ret = 6,buf DATA 1,pid = 42223
begin accept
accept from IP : 192.168.40.173 Port : 44990
ret = 6,buf DATA 0,pid = 42224
ret = 6,buf DATA 2,pid = 42223
ret = 6,buf DATA 1,pid = 42224
ret = 6,buf DATA 3,pid = 42223
ret = 6,buf DATA 2,pid = 42224
ret = 6,buf DATA 4,pid = 42223
ret = 6,buf DATA 3,pid = 42224
ret = 6,buf DATA 5,pid = 42223
ret = 6,buf DATA 4,pid = 42224
ret = 6,buf DATA 6,pid = 42223
ret = 6,buf DATA 5,pid = 42224
client has close
signal 17
accept: Interrupted system call
begin accept
ret = 6,buf DATA 6,pid = 42224
ret = 6,buf DATA 7,pid = 42224
ret = 6,buf DATA 8,pid = 42224
ret = 6,buf DATA 9,pid = 42224
ret = 7,buf DATA 10,pid = 42224
ret = 7,buf DATA 11,pid = 42224
ret = 7,buf DATA 12,pid = 42224
ret = 7,buf DATA 13,pid = 42224
client has close
signal 17
accept: Interrupted system call
begin accept


connect success…
write 6 bytes,pid = 45078
write 6 bytes,pid = 45078
write 6 bytes,pid = 45078
write 6 bytes,pid = 45078
write 6 bytes,pid = 45078
write 6 bytes,pid = 45078
write 6 bytes,pid = 45078
^C


connect success…
write 6 bytes,pid = 45079
write 6 bytes,pid = 45079
write 6 bytes,pid = 45079
write 6 bytes,pid = 45079
write 6 bytes,pid = 45079
write 6 bytes,pid = 45079
write 6 bytes,pid = 45079
write 6 bytes,pid = 45079
write 6 bytes,pid = 45079
write 6 bytes,pid = 45079
write 7 bytes,pid = 45079
write 7 bytes,pid = 45079
write 7 bytes,pid = 45079
write 7 bytes,pid = 45079
^C


示例-服务器给客户端发送消息
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d\n",sig);

	while(waitpid(-1,NULL,WNOHANG) > 0);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = 0;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGCHLD,&sigact,NULL);


	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
			continue;  
		}

		pid_t pid = fork();

		if(pid == 0)
		{
			close(listen_fd);

			char IP_buf[INET_ADDRSTRLEN];
			inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
			printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

			while(1)
			{
				int ret = read(client_fd,buf,sizeof(buf));
				if(ret == -1)
				{
					perror("read");

					if(errno == EINTR)
						continue;
					else
					{
						close(client_fd);
						break;
					}
				}
				else if(ret == 0)
				{
					printf("client has close\n");
					close(client_fd);
					break;
				}
				else   
				{
					int i;
					for(i=0;i<ret;i++)
						buf[i] = toupper(buf[i]);

					ret = write(client_fd,buf,strlen(buf));
				}
			}

			close(client_fd);
			exit(0);
		}
		else if(pid == -1)
		{
			perror("fork");
			exit(0);
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.174"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	signal(SIGPIPE,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	char buf[1024];

	while(1)
	{
		if(fgets(buf,sizeof(buf),stdin) != NULL)
		{
			ret = write(socket_fd,buf,strlen(buf));
			if(ret == -1)
			{
				perror("write");
				exit(1);
			}

			ret = read(socket_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				exit(-1);
			}

			printf("%s",buf);
		}

	}
	
	close(socket_fd);

	return 0;
}

begin accept
begin accept
accept from IP : 192.168.40.173 Port : 45000


connect success…
hello world
HELLO WORLD


示例-telnet的一个bug
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d\n",sig);

	while(waitpid(-1,NULL,WNOHANG) > 0);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = 0;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGCHLD,&sigact,NULL);


	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
			continue;  
		}

		pid_t pid = fork();

		if(pid == 0)
		{
			close(listen_fd);

			char IP_buf[INET_ADDRSTRLEN];
			inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
			printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

			dup2(client_fd,0);
			dup2(client_fd,1);
			dup2(client_fd,2);

			execl("./myshell","myshell",NULL);

			close(client_fd);
			exit(0);
		}
		else if(pid == -1)
		{
			perror("fork");
			exit(0);
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.174"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	signal(SIGPIPE,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	char buf[1024];

	int count = 0;

	while(1)
	{
		ret = read(socket_fd,buf,sizeof(buf));
		if(ret == -1)
		{
				perror("read");
			exit(-1);
		}

		printf("")

		ret = write(STDOUT_FILENO,buf,ret);
		if(ret == -1)
		{
			perror("write");
			exit(1);
		}

		if(fgets(buf,sizeof(buf),stdin) != NULL)
		{
			ret = write(socket_fd,buf,strlen(buf));
			if(ret == -1)
			{
				perror("write");
				exit(1);
			}

			ret = read(socket_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				exit(-1);
			}

			printf("count = %d\n",count++);

			ret = write(STDOUT_FILENO,buf,ret);
			if(ret == -1)
			{
				perror("write");
				exit(1);
			}
		}

	}
	
	close(socket_fd);

	return 0;
}
myshell
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

int main(int argc,char ** argv)
{
	char cmd_buf[1024];
	char * arg[10];
	int i = 0;
	pid_t pid;
	int ret;
	char cwd_buf[1024];
	char * home_ptr;

READ_AGAIN:
	getcwd(cwd_buf,sizeof(cwd_buf));
	home_ptr = getenv("HOME");

	if(strncmp(cwd_buf,home_ptr,strlen(home_ptr)) == 0)
		printf("%s@centos:~%s$ ",getenv("USER"),cwd_buf+strlen(home_ptr));
	else
		printf("%s@centos:%s$ ",getenv("USER"),getcwd(NULL,0));
    //getcwd通过PCB获取当前路径
    
	fflush(stdout);//刷新stdout流  stdout是包含在stdio.h中的全局变量

	ret = read(STDIN_FILENO,cmd_buf,sizeof(cmd_buf));
	cmd_buf[ret-1] = '\0';   //去掉回车

	if(strlen(cmd_buf) == 0)
		goto READ_AGAIN;
	
	i = 0;
	arg[0] = strtok(cmd_buf," ");
	while(arg[++i] = strtok(NULL," "));

	if(strcmp(arg[0],"cd") == 0)
	{
		if(i == 1)
		{
			chdir((char*)getenv("HOME"));
		}
		else if(i == 2)
		{
			chdir(arg[1]);
		}

		goto READ_AGAIN;
	}
	else if(strcmp(arg[0],"exit") == 0 && i==1)
	{
		exit(1);
	}

	pid = fork();
	if(pid == 0)
	{
		execvp(arg[0],arg);
	}
	else if(pid > 0)
	{
		wait(NULL);
		goto READ_AGAIN;
	}

	return 0;
}

begin accept
begin accept
accept from IP : 192.168.40.173 Port : 45028


connect success…
root@centos:~/linux-programming$
count = 0
root@centos:~/linux-programming$


正常的顺序是:读myshell提示符,写到本地屏幕,读本地命令,发到服务器,从服务器读取结果,写到本地屏幕。

出现问题的顺序:读myshell提示符,写到本地屏幕,读本地命令回车,发到服务器,回车没有结果,从服务器读取myshell提示符,写到本地屏幕,再去读socket时,被阻塞。bug出现在,不是所有命令都有输出结果,在没有输出结果时,把myshell提示符当成了输出结果,再次读取myshell提示符时被阻塞。

示例-mytelnet
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d\n",sig);

	while(waitpid(-1,NULL,WNOHANG) > 0);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = 0;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGCHLD,&sigact,NULL);


	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
			continue;  
		}

		pid_t pid = fork();

		if(pid == 0)
		{
			close(listen_fd);

			char IP_buf[INET_ADDRSTRLEN];
			inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
			printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

			dup2(client_fd,0);
			dup2(client_fd,1);
			dup2(client_fd,2);

			execl("./myshell","myshell",NULL);

			close(client_fd);
			exit(0);
		}
		else if(pid == -1)
		{
			perror("fork");
			exit(0);
		}

		close(client_fd);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.174"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	signal(SIGPIPE,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	char buf[1024];

	pid_t pid = fork();

	if(pid == 0)
	{
		while(1)
		{
			ret = read(socket_fd,buf,sizeof(buf));
			if(ret == -1)
			{
					perror("read");
				exit(-1);
			}

			ret = write(STDOUT_FILENO,buf,ret);
			if(ret == -1)
			{
				perror("write");
				exit(1);
			}
		}

		close(socket_fd);
		exit(0);
	}
	else if(pid > 0)
	{
		while(1)
		{
			if(fgets(buf,sizeof(buf),stdin) != NULL)
			{
				ret = write(socket_fd,buf,strlen(buf));
				if(ret == -1)
				{
					perror("write");
					exit(1);
				}
			}
		}
	}
	
	close(socket_fd);

	return 0;
}
myshell
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

int main(int argc,char ** argv)
{
	char cmd_buf[1024];
	char * arg[10];
	int i = 0;
	pid_t pid;
	int ret;
	char cwd_buf[1024];
	char * home_ptr;

READ_AGAIN:
	getcwd(cwd_buf,sizeof(cwd_buf));
	home_ptr = getenv("HOME");

	if(strncmp(cwd_buf,home_ptr,strlen(home_ptr)) == 0)
		printf("%s@centos:~%s$ ",getenv("USER"),cwd_buf+strlen(home_ptr));
	else
		printf("%s@centos:%s$ ",getenv("USER"),getcwd(NULL,0));
    
	fflush(stdout);

	ret = read(STDIN_FILENO,cmd_buf,sizeof(cmd_buf));
	cmd_buf[ret-1] = '\0';   //去掉回车

	if(strlen(cmd_buf) == 0)
		goto READ_AGAIN;
	
	i = 0;
	arg[0] = strtok(cmd_buf," ");
	while(arg[++i] = strtok(NULL," "));

	if(strcmp(arg[0],"cd") == 0)
	{
		if(i == 1)
		{
			chdir((char*)getenv("HOME"));
		}
		else if(i == 2)
		{
			chdir(arg[1]);
		}

		goto READ_AGAIN;
	}
	else if(strcmp(arg[0],"exit") == 0 && i==1)
	{
		exit(1);
	}

	pid = fork();
	if(pid == 0)
	{
		execvp(arg[0],arg);
	}
	else if(pid > 0)
	{
		wait(NULL);
		goto READ_AGAIN;
	}

	return 0;
}

对于myshell的信号处理,要在myshell.c中注册信号函数。执行exec以后,原来进程中注册的信号函数就消失了。因为服务器中要往socket中写入数据,所以要处理一下SIGPIPE信号。可以在myshell中注册signal 13信号处理函数。

把telnet设置成守护进程时,注意设置守护进程时把文件描述符0、1、2全部关闭了,listen_fd是0号描述符,client_fd是1号描述符,注意文件描述符之间的关系。可以在守护进程中不关闭标准文件符,在dup2时会自动关闭标准文件符。

7.5 多线程服务器模型

在使用线程模型开发服务器时需考虑以下问题:

  • 调整进程内最大文件描述符上限
  • 线程如有共享数据,考虑线程同步
  • 服务与客户端线程退出时,退出处理。(退出值,分离态)
  • 系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU
示例-多线程服务器
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <pthread.h>
#include <string.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d,thread ID %lx\n",sig,(long)pthread_self());
}

void * my_thread(void * arg)
{
	pthread_detach(pthread_self());

	int client_fd = (int)arg;

	char buf[MAX_BYTES];

	while(1)
	{
		int ret = read(client_fd,buf,sizeof(buf));
		printf("read len = %d\n",ret);

		if(ret == -1)
		{
			perror("read");

			if(errno == EINTR)
				continue;
			else
			{
				close(client_fd);
				break;
			}
		}
		else if(ret == 0)
		{
			printf("client has close\n");
			close(client_fd);
			break;
		}
		else   
		{
			int i;
			for(i=0;i<ret;i++)
				buf[i] = toupper(buf[i]);

			ret = write(client_fd,buf,ret);
			if(ret == -1)
			{
				perror("write");
				exit(-1);
			}
		}
	}

	close(client_fd);
	pthread_exit(0);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = 0;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGPIPE,&sigact,NULL);

	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8886);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
			continue;  
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		pthread_t th_id;
		ret = pthread_create(&th_id,NULL,my_thread,(void*)client_fd);
		if(ret != 0)
		{
			fputs(strerror(ret),stderr);
			exit(ret);
		}
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8886
#define SERVER_IP "192.168.40.174"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	signal(SIGPIPE,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	char buf[1024];

	while(1)
	{
		bzero(buf,sizeof(buf));
		if(fgets(buf,sizeof(buf),stdin) != NULL)
		{
			ret = write(socket_fd,buf,strlen(buf));
			if(ret == -1)
			{
				perror("write");
				exit(1);
			}

			ret = read(socket_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				exit(-1);
			}

			ret = write(STDOUT_FILENO,buf,ret);
			if(ret == -1)
			{
				perror("write");
				exit(1);
			}
		}

	}
	
	close(socket_fd);

	return 0;
}

socket读端关闭以后,再写两次会产生SIGPIPE信号。在此时的线程中,若在read之后,write之前,读端关闭,接下来的第一次写是不会收到SIGPIPE信号。再次来到read,发现读到0,所以推出线程。不会产生SIGPIPE信号。若是连续写多次,则可能会造成管道破裂。

示例-多线程服务器中处理管道破裂
服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <pthread.h>
#include <string.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d,thread ID %lx\n",sig,(long)pthread_self());
}

void * my_thread(void * arg)
{
	pthread_detach(pthread_self());

	int client_fd = (int)arg;

	char buf[MAX_BYTES];
	int i = 0;

	while(1)
	{	
		sprintf(buf,"DATA %d",i++);

		int ret = write(client_fd,buf,strlen(buf));
		printf("write success %s,thread ID %lx\n",buf,(long)pthread_self());

		sleep(1);

	}

	close(client_fd);
	pthread_exit(0);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = 0;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGPIPE,&sigact,NULL);

	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8883);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
			continue;  
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		pthread_t th_id;
		ret = pthread_create(&th_id,NULL,my_thread,(void*)client_fd);
		if(ret != 0)
		{
			fputs(strerror(ret),stderr);
			exit(ret);
		}
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8883
#define SERVER_IP "192.168.40.174"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	signal(SIGPIPE,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	char buf[1024];

	while(1)
	{
		ret = read(socket_fd,buf,sizeof(buf));
		if(ret == -1)
		{
			perror("read");
			exit(-1);
		}

		printf("%s\n",buf);

	}
	
	close(socket_fd);

	return 0;
}

begin accept
accept from IP : 192.168.40.173 Port : 56142
begin accept
write success DATA 0,thread ID 7fec7738d700
write success DATA 1,thread ID 7fec7738d700
write success DATA 2,thread ID 7fec7738d700
accept from IP : 192.168.40.173 Port : 56144
begin accept
write success DATA 0,thread ID 7fec76b8c700
write success DATA 3,thread ID 7fec7738d700
write success DATA 1,thread ID 7fec76b8c700
write success DATA 4,thread ID 7fec7738d700
write success DATA 2,thread ID 7fec76b8c700
write success DATA 5,thread ID 7fec7738d700
write success DATA 3,thread ID 7fec76b8c700
write success DATA 6,thread ID 7fec7738d700
write success DATA 4,thread ID 7fec76b8c700
write success DATA 7,thread ID 7fec7738d700
write success DATA 5,thread ID 7fec76b8c700
write success DATA 8,thread ID 7fec7738d700
write success DATA 6,thread ID 7fec76b8c700
write success DATA 9,thread ID 7fec7738d700
signal 13,thread ID 7fec76b8c700
write success DATA 7,thread ID 7fec76b8c700
write success DATA 10,thread ID 7fec7738d700
signal 13,thread ID 7fec76b8c700
write success DATA 8,thread ID 7fec76b8c700
write success DATA 11,thread ID 7fec7738d700
signal 13,thread ID 7fec76b8c700
write success DATA 9,thread ID 7fec76b8c700
write success DATA 12,thread ID 7fec7738d700
signal 13,thread ID 7fec76b8c700
write success DATA 10,thread ID 7fec76b8c700
write success DATA 13,thread ID 7fec7738d700
signal 13,thread ID 7fec76b8c700
write success DATA 11,thread ID 7fec76b8c700
^C


connect success…
DATA 0
DATA 1
DATA 2
DATA 3
DATA 4
^C


connect success…
DATA 0
DATA 1
DATA 2
DATA 3
DATA 4 重复打印DATA4


多个线程,哪个线程被信号打断了,哪个线程去处理信号函数。

示例-线程池模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ePTDLMbG-1572697158135)(第七章 网络编程.assets/多线程模型.png)]存在惊群效应的模型,可以加上互斥锁。

服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <pthread.h>
#include <string.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d,thread ID %lx\n",sig,(long)pthread_self());
}

void * my_thread(void * arg)
{
	pthread_detach(pthread_self());
	
	struct sockaddr_in client_addr;
		
	bzero(&client_addr,sizeof(client_addr));
	socklen_t addr_len = sizeof(client_addr);

	while(1)
	{
		puts("begin accept");

		int listen_fd = (int)arg;

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
			continue;  
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d thread ID : %ld\n",IP_buf,ntohs(client_addr.sin_port),(long)pthread_self());

		char buf[MAX_BYTES];
		int i = 0;

		while(1)
		{	
			int ret = read(client_fd,buf,sizeof(buf));
				if(ret == -1)
				{
					perror("read");

					if(errno == EINTR)
						continue;
					else
					{
						close(client_fd);
						break;
					}
				}
				else if(ret == 0)
				{
					printf("client has close\n");
					close(client_fd);
					break;
				}
				else   
				{
					int i;
					for(i=0;i<ret;i++)
						buf[i] = toupper(buf[i]);

					ret = write(client_fd,buf,strlen(buf));
				}
		}

		close(client_fd);
	}

	pthread_exit(0);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = 0;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGPIPE,&sigact,NULL);

	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	int i;
	pthread_t th_id;

	for(i=0;i<10;i++)
	{
		ret = pthread_create(&th_id,NULL,my_thread,(void*)listen_fd);
		if(ret != 0)
		{
			fputs(strerror(ret),stderr);
			exit(ret);
		}
	}

	while(1)
	{
		sleep(1);
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.174"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	signal(SIGPIPE,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	char buf[1024];

	while(1)
	{
		if(fgets(buf,sizeof(buf),stdin) != NULL)
		{
			ret = write(socket_fd,buf,strlen(buf));
			if(ret == -1)
			{
				perror("write");
				exit(1);
			}

			ret = read(socket_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				exit(-1);
			}

			printf("%s",buf);
		}

	}
	
	close(socket_fd);

	return 0;
}

begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
accept from IP : 192.168.40.173 Port : 36346 thread ID : 139940649711360
accept from IP : 192.168.40.173 Port : 36348 thread ID : 139940658104064
client has close
begin accept
client has close
begin accept


connect success…
Hello world
HELLO WORLD
^C


connect success…
haha iiii
HAHA IIII
^C


7.6 TCP编程

7.6.1 TCP状态转换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Uw91Z5F-1572697158136)(第七章 网络编程.assets/1565925665459.png)]

1、建立连接协议(三次握手)
(1)客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的报文1。
(2) 服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和SYN标志。因此它表示对
刚才客户端SYN报文的回应;同时又发送标志SYN给客户端,询问客户端是否准备好进行数据通讯。
(3) 客户必须再次回应服务器一个ACK报文,这是报文段3。
2、连接终止协议(四次握手)
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能
发送一个FIN来终止这个方向的连接。收到一个FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个
FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送(报文段4)。
(2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占
用一个序号。
(3) 服务器关闭客户端的连接,发送一个FIN给客户端(报文段6)。
(4) 客户段发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。

 CLOSED: #这个没什么好说的了,表示初始状态。
LISTEN: #这个也是非常容易理解的一个状态,表示服务器端的某个SOCKET处于监听状态,可以接受连接了。
SYN_RCVD: #这个状态表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次
握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测
试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文后,它会进
入到ESTABLISHED状态。
SYN_SENT: #这个状态与SYN_RCVD遥想呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即
它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报
文。
ESTABLISHED:#这个容易理解了,表示连接已经建立了。
FIN_WAIT_1: #这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报
文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方
发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当
然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,
而FIN_WAIT_2状态还有时常常可以用netstat看到,如果长时间没有收到ACK,过一段时间后也会重置为CLOSED状态。
FIN_WAIT_2:#上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求
close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。
TIME_WAIT: #表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果
FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过
FIN_WAIT_2状态。
CLOSING: #这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN
报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送
FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一 
下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情
况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
CLOSE_WAIT: #这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自
己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑
的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以close这个SOCKET,发送FIN报文给对方,
也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。
LAST_ACK: #这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收
到ACK报文后,也即可以进入到CLOSED可用状态了。

套接字端口的状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NJHvAv1I-1572697158137)(第七章 网络编程.assets/TCP状态转换.png)]


服务器端先启动

root@ubuntu:~/linux-programming# ./multi_process_server
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept

root@ubuntu:~# netstat -anp | grep 8888
tcp 0 0 0.0.0.0:8888 0.0.0.0:* LISTEN 2689/multi_process_

0.0.0.0:8888:任意IP的端口8888都可以访问,0.0.0.0:*:任意IP的任意端口都可以访问。

listen让端口号具有监听的能力,而accept让端口号处于监听的状态。


客户端再启动

root@ubuntu:~/linux-programming# ./tcp_client
connect success…

root@ubuntu:~/linux-programming# netstat -anp | grep 8888
tcp 0 0 192.168.40.173:36350 192.168.40.174:8888 ESTABLISHED 2663/tcp_client


客户端启动后,服务器的状态。

root@ubuntu:~/linux-programming# ./multi_process_server
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
begin accept
accept from IP : 192.168.40.173 Port : 36350 thread ID : 140130580395776

root@ubuntu:~# netstat -anp | grep 8888
tcp 0 0 0.0.0.0:8888 0.0.0.0:* LISTEN 2689/multi_process_
tcp 0 0 192.168.40.174:8888 192.168.40.173:36350 ESTABLISHED 2689/multi_process_


客户端先关闭,但服务端不调用close。

服务端

root@ubuntu:~# netstat -anp | grep 8888
tcp 0 0 0.0.0.0:8888 0.0.0.0:* LISTEN 2909/multi_process_
tcp 0 0 192.168.40.174:8888 192.168.40.173:36358 CLOSE_WAIT 2909/multi_process_

客户端

root@ubuntu:~/linux-programming# netstat -anp | grep 8888
tcp 0 0 192.168.40.173:36358 192.168.40.174:8888 FIN_WAIT2 -


服务端先关闭,但客户端端不调用close。

服务端

root@ubuntu:~# netstat -anp | grep 8888
tcp 0 0 192.168.40.174:8888 192.168.40.173:36360 FIN_WAIT2 -

客户端

root@ubuntu:~/linux-programming#
root@ubuntu:~/linux-programming# netstat -anp | grep 8888
tcp 1 0 192.168.40.173:36360 192.168.40.174:8888 CLOSE_WAIT 2765/tcp_client


客户端先关闭,服务端调用close。

客户端

root@ubuntu:~/linux-programming# netstat -anp | grep 8888
tcp 0 0 192.168.40.173:36362 192.168.40.174:8888 TIME_WAIT -

服务端

root@ubuntu:~# netstat -anp | grep 8888
tcp 0 0 0.0.0.0:8888 0.0.0.0:* LISTEN 2966/multi_process_

TIME_WAIT会继续持续2MSL,两倍的片最大生存时间,大约是30s。

7.6.2 TCP流量控制(滑动窗口)

介绍UDP时我们描述了这样的问题:如果发送端发送的速度较快,接收端接收到数据后处理的速度较慢,而接收缓冲区的大小是固定的,就会丢失数据。TCP协议通过滑动窗口(Sliding Window)机制解决这一问题。看下图的通讯过程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GUCiLFst-1572697158137)(第七章 网络编程.assets/1565925717314.png)]

  1. 发送端发起连接,声明最大段尺寸是1460,初始序号是0,窗口大小是4K,表示“我的接收缓冲区还有4K字节空闲,你发的数据不要超过4K”。接收端应答连接请求,声明最大段尺寸是1024,初始序号是8000,窗口大小是6K。发送端应答,三方握手结束。
  2. 发送端发出段4­9,每个段带1K的数据,发送端根据窗口大小知道接收端的缓冲区满了,因此停止发送数据。
  3. 接收端的应用程序提走2K数据,接收缓冲区又有了2K空闲,接收端发出段10,在应答已收到6K数据的同时声明窗口大小为2K。
  4. 接收端的应用程序又提走2K数据,接收缓冲区有4K空闲,接收端发出段11,重新声明窗口大小为4K。
  5. 发送端发出段12­13,每个段带2K数据,段13同时还包含FIN位。
  6. 接收端应答接收到的2K数据(6145­8192),再加上FIN位占一个序号8193,因此应答序号是8194,连接处于半关闭状态,接收端同时声明窗口大小为2K。
  7. 接收端的应用程序提走2K数据,接收端重新声明窗口大小为4K。
  8. 接收端的应用程序提走剩下的2K数据,接收缓冲区全空,接收端重新声明窗口大小为6K。
  9. 接收端的应用程序在提走全部数据后,决定关闭连接,发出段17包含FIN位,发送端应答,连接完全关闭。

上图在接收端用小方块表示1K数据,实心的小方块表示已接收到的数据,虚线框表示接收缓冲区,因此套在虚线框中的空心小方块表示窗口大小,从图中可以看出,随着应用程序提走数据,虚线框是向右滑动的,因此称为滑动窗口。

从这个例子还可以看出,发送端是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),在底层通讯中这些数据可能被拆成很多数据包来发送,但是一个数据包有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。

7.6.3 TCP与UDP的不同接包处理方式

1.UDP发包的问题
问:udp 发送两次数据,第一次 100字节 ,第二次200字节, 接包方一次recvfrom( 1000 ), 收到是 100,还是200,还是300?
答:UDP是数据报文协议,是以数据包方式,所以每次可以接收100,200,在理想情况下,第一次是无论recvfrom多少都是接收到100。当然,可能由于网络原因,第二个包先到的话,有可能是200了。对可能会由于网络原因乱序,所以可能先收到200,所以自定义的udp协议包头里都要加上一个序列号,标识发送与收包对应2.TCP的发包问题
问:同样如果换成tcp, 第一次发送 100字节 ,第二次发送200字节,recv( 1000 )会接收到多少?
答:tcp是流协议,所以recv( 1000 ),会收到300 tcp自己处理好了重传,保证数据包的完整性
3.有分片的情况下如下处理
问:如果MTU是1500,使用UDP发送 2000,那么recvfrom(2000)是收到1500,还是2000?
答: 还是接收2000,数据分片由IP层处理了,放到UDP还是一个完整的包。接收到的包是由路由路径上最少的MTU来分片,注意转到UDP已经在是组装好的(组装出错的包会经crc校验出错而丢弃),是一个完整的数据包
4.分片后的处理
问:如果500那个片丢了怎么办?udp又没有重传
答:UDP里有个CRC检验,如果包不完整就会丢弃,也不会通知是否接收成功,所以UDP是不可靠的传输协议,而且TCP不存在这个问题,有自己的重传机制。在内网来说,UDP基本不会有丢包,可靠性还是有保障。当然如果是要求有时序性和高可靠性,还是走TCP,不然就要自己提供重传和乱序处理( UDP内网发包处理量可以达7w~10w/s )

5.不同连接到同一个端口的包处理
问:TCP
A ­> C 发100
B ­> C 发200

AB同时同一端口
C recv(1000) ,会收到多少?
答:A与C是一个tcp连接,B与C又是另一个tcp连接, 所以不同socket,所以分开处理。每个socket有自己的接收缓冲和发送缓冲
6.什么是TCP粘包
由于TCP是流协议,对于一个socket的包,如发送 10AAAAABBBBB两次,由于网络原因第一次又分成两次发送, 10AAAAAB和BBBB,如果接包的时候先读取10(包长度)再读入后续数据,当接收得快,发送的慢时,就会出现先接收了 10AAAAAB,会解释错误 ,再接到到BBBB10AAAAABBBBB,也解释错误的情况。这就是TCP的粘包。
解决的办法TLV方式,先接收包头,在包头里指定包体长度来接收。设置包头包尾的检查位(如群空间0x2开头,0x3结束来检查一个包是否完整)。对于TCP来说:1)不存在丢包,错包,所以不会出现数据出错 2)如果包头检测错误,即为非法或者请求,直接重置即可

7.7 IO复用服务器模型

多进程和多线程模型,消耗大量的时间在执行单位之间切换。多个进程或线程处在阻塞的状态,数据到达时,在调度到CPU。

单进程内有多个socket,socket是非阻塞的,在多个socket之间不停的轮询。

7.7.1 select

  • select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数。
  • 解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力。
函数原型-select
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
readfds:监控有读数据到达文件描述符集合,传入传出参数
writefds:监控写数据到达文件描述符集合,传入传出参数
exceptfds:监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
timeout:定时阻塞监控时间,3种情况

  • NULL,永远等下去
  • 设置timeval,等待固定时间。
  • 设置timeval里时间均为0,检查描述字后立即返回,轮询。
struct timeval 
{
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
};

void FD_CLR(int fd, fd_set *set);   //把文件描述符集合里fd清0
int FD_ISSET(int fd, fd_set *set);  //测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set);   //把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set);          //把文件描述符集合里所有位清0

监听套接字listen_socket若是可读时,表示可以建立链接了,需要使用accept函数去处理listen_socket。listen_socket在收到SYN时,则是可读的,说明这是要建立三次握手了。accept函数在处理listen_socket时就是在读这个套接字的时候被阻塞了,直到有一个客户来链接,这个套接字才可以读。现在把listen_socket加到反应堆中,若是可读了,表示有客户需要建立链接,此时需要accept函数去处理。

示例-select服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <ctype.h>

#define MAX_LINE 128
#define MAX_BYTES 4096
#define PORT 8888

int main()
{
    char buf[MAX_BYTES];

    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    int client_fd;
    int listen_fd = socket(AF_INET,SOCK_STREAM,0);

    int ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if(ret == -1)
    {
        perror("bind");
        exit(-1);
    }

    listen(listen_fd,128);

    int nready;
    int max_fd = listen_fd;

    int i,max_i = -1;
    int client[FD_SETSIZE];
    for(i=0;i<FD_SETSIZE;i++)
    {
        client[i] = -1;
    }

    fd_set rset,allset;
    FD_ZERO(&allset);
    FD_SET(listen_fd,&allset);

    struct sockaddr_in client_addr;

    while(1)
    {
        rset = allset;

        puts("select...");
        
        nready = select(max_fd+1,&rset,NULL,NULL,NULL);
        if(nready < 0)
        {
            perror("select");
            exit(-1);
        }

        //解复用
        if(FD_ISSET(listen_fd,&rset))
        {
            //创建新的链接
            bzero(&client_addr,sizeof(client_addr));
            socklen_t addr_len = sizeof(client_addr);

            client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
            if(client_fd == -1)
            {
                perror("accept");
                exit(-1);
            }

            char IP_buf[INET_ADDRSTRLEN];
            inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
            printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

            //保存accept返回的文件描述符到client[]里。
            for(i = 0;i<FD_SETSIZE;i++)
            {
                if(client[i] < 0)
                {
                    client[i] = client_fd;
                    break;
                }
            }

            if(i == FD_SETSIZE)
            {
                fputs("too many clients\n",stderr);
                exit(-1);
            }

            //添加一个新的文件描述符到监控信号集里
            FD_SET(client_fd,&allset);

            if(client_fd > max_fd)
            {
                max_fd = client_fd;
            }

            if(i > max_i)
            {
                max_i = i;
            }

            if(--nready == 0)
            {
                continue;
            }
        }

        for(i=0;i<= max_i;i++)
        {
            int socket_fd;
            if((socket_fd=client[i]) < 0)
            {
                continue;
            }

            if(FD_ISSET(socket_fd,&rset))
            {
                int ret = read(socket_fd,buf,sizeof(buf));
                if(ret == -1)
                {
                    perror("read");
                    exit(-1);
                }
                else if(ret == 0)
                {
                    printf("client has closed\n");
                    close(socket_fd);
                    FD_CLR(socket_fd,&allset);
                    client[i] = -1;
                }
                else
                {
                    buf[ret] = '\0';

                    printf("read : %s",buf);

                    int j = 0;
                    for(j=0;j<ret;j++)
                    {
                        buf[j] = toupper(buf[j]);
                    }

                    ret = write(socket_fd,buf,ret);
                    if(ret == -1)
                    {
                        perror("write");
                        exit(-1);
                    }
                }

                if(--nready == 0)
                {
                    break;
                }
            }
        }
    }

    close(listen_fd);

    return 0;
}

select…
accept from IP : 192.168.40.173 Port : 36398
select…
read : hello world
select…
accept from IP : 192.168.40.173 Port : 36400
select…
read : biubiu
select…
client has closed
select…
read : haha
select…
client has closed
select…
accept from IP : 192.168.40.173 Port : 36402
select…
read : heihei
select…
client has closed
select…


root@ubuntu:~/linux-programming# ./tcp_client
connect success…
hello world
HELLO WORLD
haha
HAHA
^C
root@ubuntu:~/linux-programming# ./tcp_client
connect success…
heihei
HEIHEI
^C


root@ubuntu:~/linux-programming# ./tcp_client
connect success…
biubiu
BIUBIU
^C


7.7.2 poll

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* 文件描述符*/    
short events; /* 监控的事件*/    
short revents; /* 监控事件中满足条件返回的事件*/    
};
/*
POLLIN普通或带外优先数据可读,即POLLRDNORM | POLLRDBAND
POLLRDNORM‐数据可读
POLLRDBAND‐优先级带数据可读
POLLPRI 高优先级可读数据
POLLOUT普通或带外数据可写
POLLWRNORM‐数据可写
POLLWRBAND‐优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件
nfds 监控数组中有多少文件描述符需要被监控
timeout 毫秒级等待
‐1:阻塞等,#define INFTIM ‐1 Linux中没有定义此宏
0:立即返回,不阻塞进程
>0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值
*/

如果不再监控某个文件描述符时,可以把pollfd中,fd设置为­1,poll不再监控此pollfd,下次返回时,把revents设置为0。

7.7.3 epoll

epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

目前epoll是linux大规模并发网络程序中的热门首选模型。

epoll除了提供select/ poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。一个进程打开大数目的socket描述符。

sudo vi /etc/security/limits.conf
#写入以下配置,soft软限制,hard硬限制
* soft nofile 65536
* hard nofile 100000
epoll API

1、创建一个epoll句柄,参数size用来告诉内核监听的文件描述符个数,跟内存大小有关。

int epoll_create(int size)
//size:告诉内核监听的数目

2、控制某个epoll监控的文件描述符上的事件:注册、修改、删除。

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

epfd:为epoll_creat的句柄
op:表示动作,用3个宏来表示:

EPOLL_CTL_ADD //(注册新的fd到epfd),
EPOLL_CTL_MOD //(修改已经注册的fd的监听事件),
EPOLL_CTL_DEL //(从epfd删除一个fd);

event:告诉内核需要监听的事件

struct epoll_event {
_uint32t events; /* Epoll events */    
epoll_data_t data; /* User data variable */    
};
typedef union epoll_data 
{
    void     *ptr;
    int      fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

EPOLLIN :#表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT:#表示对应的文件描述符可以写
EPOLLPRI:#表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR:#表示对应的文件描述符发生错误
EPOLLHUP:#表示对应的文件描述符被挂断;
EPOLLET: #将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
EPOLLONESHOT:#只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

等待所监控文件描述符上有事件的产生,类似于select()调用。

水平触发是,内核每隔一小段时间就去检测一次,缺点是检测不及时。边缘触发是在变化的上升沿就可以捕捉变化,响应及时,缺点是错过了变化的上升沿就无法检测到结果了。

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

events:用来从内核得到事件的集合,
maxevents:告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
timeout:是超时时间
-1:阻塞
0:立即返回,非阻塞
> 0:指定微秒
返回值:成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1。

服务器
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <pthread.h>
#include <string.h>

#define MAX_BYTES 4096

void sig_handler(int sig)
{
	printf("signal %d,thread ID %lx\n",sig,(long)pthread_self());
}

void * my_thread(void * arg)
{
	pthread_detach(pthread_self());

	int client_fd = (int)arg;

	char buf[MAX_BYTES];

	while(1)
	{
		int ret = read(client_fd,buf,sizeof(buf));
		printf("read len = %d\n",ret);

		if(ret == -1)
		{
			perror("read");

			if(errno == EINTR)
				continue;
			else
			{
				close(client_fd);
				break;
			}
		}
		else if(ret == 0)
		{
			printf("client has close\n");
			close(client_fd);
			break;
		}
		else   
		{
			int i;
			for(i=0;i<ret;i++)
				buf[i] = toupper(buf[i]);

			ret = write(client_fd,buf,ret);
			if(ret == -1)
			{
				perror("write");
				exit(-1);
			}
		}
	}

	close(client_fd);
	pthread_exit(0);
}

int main()
{
	struct sigaction sigact;
	sigact.sa_flags = 0;
	sigact.sa_handler = sig_handler;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGPIPE,&sigact,NULL);

	char buf[MAX_BYTES];

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8886);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int listen_fd = socket(AF_INET,SOCK_STREAM,0);

	int flag = 1;
	int len = sizeof(flag);

	int ret = setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&flag,len);
	if(ret == -1)
	{
		perror("setsockopt");
		exit(-1);
	}

	ret = bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)	
	{
		perror("bind");
		exit(errno);
	}

	listen(listen_fd,128);

	struct sockaddr_in client_addr;

	while(1)
	{
		bzero(&client_addr,sizeof(client_addr));
		socklen_t addr_len = sizeof(client_addr);

		puts("begin accept");

		int client_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&addr_len);
		if(client_fd == -1)
		{
			perror("accept");
			continue;  
		}

		char IP_buf[INET_ADDRSTRLEN];
		inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));
		printf("accept from IP : %s Port : %d\n",IP_buf,ntohs(client_addr.sin_port));

		pthread_t th_id;
		ret = pthread_create(&th_id,NULL,my_thread,(void*)client_fd);
		if(ret != 0)
		{
			fputs(strerror(ret),stderr);
			exit(ret);
		}
	}

	close(listen_fd);
	return 0;
}
客户端
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8886
#define SERVER_IP "192.168.40.174"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	signal(SIGPIPE,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	char buf[1024];

	while(1)
	{
		bzero(buf,sizeof(buf));
		if(fgets(buf,sizeof(buf),stdin) != NULL)
		{
			ret = write(socket_fd,buf,strlen(buf));
			if(ret == -1)
			{
				perror("write");
				exit(1);
			}

			ret = read(socket_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				exit(-1);
			}

			ret = write(STDOUT_FILENO,buf,ret);
			if(ret == -1)
			{
				perror("write");
				exit(1);
			}
		}

	}
	
	close(socket_fd);

	return 0;
}

开启服务器和客户端,然后关闭服务器。

root@ubuntu:~/linux-programming# ./server
begin accept
accept from IP : 192.168.40.175 Port : 50874
begin accept
read len = 12
^C

root@ubuntu:~# netstat -anp | grep 8886
tcp 0 0 192.168.40.174:8886 192.168.40.175:50874 FIN_WAIT2 -


再次开启服务器和客户端

root@ubuntu:~/linux-programming# ./server
begin accept
accept from IP : 192.168.40.175 Port : 50876
begin accept


root@ubuntu:~# netstat -anp | grep 8886
tcp 0 0 0.0.0.0:8886 0.0.0.0:* LISTEN 7731/server
tcp 0 0 192.168.40.174:8886 192.168.40.175:50874 FIN_WAIT2 -
tcp 0 0 192.168.40.174:8886 192.168.40.175:50876 ESTABLISHED 7731/server


设置端口复用必须在socket之后,bind之前。

服务端

#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.255"

int main()
{
	int client_addr = socket(AF_INET,SOCK_DGRAM,0);

	int flag = 1;
	setsockopt(client_addr,SOL_SOCKET,SO_BROADCAST,&flag,sizeof(flag));


	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	char buf[4096];
	int i = 0;

	while(1)
	{
		sprintf(buf,"DATA %d",i++);

		int ret = sendto(client_addr,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
		if(ret == -1)
		{
			perror("sendto");
		}
		else
		{
			printf("send success %d\n",ret);
		}

		sleep(1);
	}

	close(client_addr);

	return 0;
}

客户端

#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main()
{
	int server_fd = socket(AF_INET,SOCK_DGRAM,0);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int ret = bind(server_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("bind");
		exit(errno);
	}

	char buf[4096];

	struct sockaddr_in client_addr;
	bzero(&client_addr,sizeof(client_addr));

	socklen_t socklen = sizeof(client_addr);

	while(1)
	{
		ret = recvfrom(server_fd,buf,sizeof(buf),0,(struct sockaddr*)&client_addr,&socklen);
		if(ret == -1)
		{
			perror("recvfrom");
		}
		else
		{
			buf[ret] = '\0';

			char IP_buf[INET_ADDRSTRLEN];
			inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));

			printf("recvfrom IP %s Port %d buf: %s ret %d\n",IP_buf,ntohs(client_addr.sin_port),buf,ret);
		}
	}

	close(server_fd);

	return 0;
}

send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 7


recvfrom IP 192.168.40.175 Port 39691 buf: DATA 7 ret 6
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 8 ret 6
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 9 ret 6
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 10 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 11 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 12 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 13 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 14 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 15 ret 7


recvfrom IP 192.168.40.175 Port 39691 buf: DATA 17 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 18 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 19 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 20 ret 7


#include <stdio.h>
#include <<netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main()
{
	struct hostent * hostentp = gethostbyname("www.baidu.com");
	if(hostentp)
	{
		printf("h_name : %s\n",hostentp->h_name);

		int i = 0;
		while(hostentp->h_aliases[i])
		{
			printf("h_aliases : %s\n",hostentp->h_aliases[i++]);
		}

		i = 0;
		char buf[16];
		while(hostentp->h_addr_list[i])
		{
			inet_ntop(AF_INET,hostentp->h_addr_list[i++],buf,sizeof(buf));
			printf("h_aliases : %s\n",buf);
		}
	}

	return 0;
}

地址中存放的是无符号整型

read_create(&th_id,NULL,my_thread,(void*)client_fd);
if(ret != 0)
{
fputs(strerror(ret),stderr);
exit(ret);
}
}

close(listen_fd);
return 0;

}


###### <font color=red>客户端</font>

```c
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define SERVER_PORT 8886
#define SERVER_IP "192.168.40.174"

void sig_handler(int sig)
{
	printf("signal %d\n",sig);
}

int main()
{
	signal(SIGPIPE,sig_handler);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	int socket_fd = socket(AF_INET,SOCK_STREAM,0);

	int ret = connect(socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}

	printf("connect success...\n");

	char buf[1024];

	while(1)
	{
		bzero(buf,sizeof(buf));
		if(fgets(buf,sizeof(buf),stdin) != NULL)
		{
			ret = write(socket_fd,buf,strlen(buf));
			if(ret == -1)
			{
				perror("write");
				exit(1);
			}

			ret = read(socket_fd,buf,sizeof(buf));
			if(ret == -1)
			{
				perror("read");
				exit(-1);
			}

			ret = write(STDOUT_FILENO,buf,ret);
			if(ret == -1)
			{
				perror("write");
				exit(1);
			}
		}

	}
	
	close(socket_fd);

	return 0;
}

开启服务器和客户端,然后关闭服务器。

root@ubuntu:~/linux-programming# ./server
begin accept
accept from IP : 192.168.40.175 Port : 50874
begin accept
read len = 12
^C

root@ubuntu:~# netstat -anp | grep 8886
tcp 0 0 192.168.40.174:8886 192.168.40.175:50874 FIN_WAIT2 -


再次开启服务器和客户端

root@ubuntu:~/linux-programming# ./server
begin accept
accept from IP : 192.168.40.175 Port : 50876
begin accept


root@ubuntu:~# netstat -anp | grep 8886
tcp 0 0 0.0.0.0:8886 0.0.0.0:* LISTEN 7731/server
tcp 0 0 192.168.40.174:8886 192.168.40.175:50874 FIN_WAIT2 -
tcp 0 0 192.168.40.174:8886 192.168.40.175:50876 ESTABLISHED 7731/server


设置端口复用必须在socket之后,bind之前。

服务端

#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define SERVER_PORT 8888
#define SERVER_IP "192.168.40.255"

int main()
{
	int client_addr = socket(AF_INET,SOCK_DGRAM,0);

	int flag = 1;
	setsockopt(client_addr,SOL_SOCKET,SO_BROADCAST,&flag,sizeof(flag));


	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr.s_addr);

	char buf[4096];
	int i = 0;

	while(1)
	{
		sprintf(buf,"DATA %d",i++);

		int ret = sendto(client_addr,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
		if(ret == -1)
		{
			perror("sendto");
		}
		else
		{
			printf("send success %d\n",ret);
		}

		sleep(1);
	}

	close(client_addr);

	return 0;
}

客户端

#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main()
{
	int server_fd = socket(AF_INET,SOCK_DGRAM,0);

	struct sockaddr_in server_addr;
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int ret = bind(server_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
	if(ret == -1)
	{
		perror("bind");
		exit(errno);
	}

	char buf[4096];

	struct sockaddr_in client_addr;
	bzero(&client_addr,sizeof(client_addr));

	socklen_t socklen = sizeof(client_addr);

	while(1)
	{
		ret = recvfrom(server_fd,buf,sizeof(buf),0,(struct sockaddr*)&client_addr,&socklen);
		if(ret == -1)
		{
			perror("recvfrom");
		}
		else
		{
			buf[ret] = '\0';

			char IP_buf[INET_ADDRSTRLEN];
			inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,IP_buf,sizeof(IP_buf));

			printf("recvfrom IP %s Port %d buf: %s ret %d\n",IP_buf,ntohs(client_addr.sin_port),buf,ret);
		}
	}

	close(server_fd);

	return 0;
}

send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 6
send success 7


recvfrom IP 192.168.40.175 Port 39691 buf: DATA 7 ret 6
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 8 ret 6
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 9 ret 6
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 10 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 11 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 12 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 13 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 14 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 15 ret 7


recvfrom IP 192.168.40.175 Port 39691 buf: DATA 17 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 18 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 19 ret 7
recvfrom IP 192.168.40.175 Port 39691 buf: DATA 20 ret 7


#include <stdio.h>
#include <<netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main()
{
	struct hostent * hostentp = gethostbyname("www.baidu.com");
	if(hostentp)
	{
		printf("h_name : %s\n",hostentp->h_name);

		int i = 0;
		while(hostentp->h_aliases[i])
		{
			printf("h_aliases : %s\n",hostentp->h_aliases[i++]);
		}

		i = 0;
		char buf[16];
		while(hostentp->h_addr_list[i])
		{
			inet_ntop(AF_INET,hostentp->h_addr_list[i++],buf,sizeof(buf));
			printf("h_aliases : %s\n",buf);
		}
	}

	return 0;
}

地址中存放的是无符号整型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值