TCP IP网络编程

文章目录

  • TCP IP网络编程
    • 一、基础知识(TCP)
      • 1)Linux
        • 1. socket()
        • 2.bind()
          • 2.1前提
          • 2.2字节序与网络字节序
          • 2.3 字节序转换
          • 2.4 字符串信息转化成网络字节序的整数型
          • 2.5 INADDR_ANY
        • 3.listen()
        • 4.accept()
        • 5.connect()
        • 6.案例小结
          • 6.1服务器端
          • 6.2 客户端
        • 7.半关闭
      • 2)Window
        • 1. socket()
        • 2.bind()
        • 3.listen()
        • 4.accept()
        • 5.recv()
        • 6.send()
    • 二、UDP基础知识
      • 1)Linux
        • 1.sendto()
        • 2.recvfrom()
        • 3.已连接UDP套接字
        • 4.案例
      • 2)Window
    • 三、域名
      • 1.由域名得到ip
      • 2.通过IP地址获取域名
      • 3.Window
    • 四、套接字的多种选项
      • 1.获取和设置套接字选项
      • 2.各种选项
      • 3.Nagle算法
    • 五、多进程服务端
      • 1.案例
      • 2.分割I/O
    • 六、进程间通信
      • 1.基于管道(PIPE)的通信
    • 七、并发服务器
      • 1.I/O复用
        • 1.1 select()
        • 1.2.epoll()
          • 1.2.2 案例
          • 1.2.3边沿触发与条件触发
      • 2.多进程实现并发
      • 3.多线程服务器端
        • 3.1 创建线程
        • 3.2 等待线程的消亡
        • 3. 3 互斥量
        • 3.4 信号量
    • 八、多种I/O函数
      • 1.send(),recv()
      • 2.readv(),writev()
    • 九、多播和广播
      • 1.设置TTL
      • 2.加入多播组
      • 3.广播
    • 十、Linux编程
      • 1.标准IO与系统IO之间的相互转化
      • 2.复制文件描述符dup
      • 3.实现半关闭

TCP IP网络编程

一、基础知识(TCP)

1)Linux

1. socket()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
/*
domain: 套接字中使用的协议族信息
type : 套接字数据传输类型信息
protocol :计算机间通信中使用的协议信息。【最终决定采用什么协议】
*/
int socket(int domain, int type, int protocol);

domain

名称描述
PF_INETIPv4互联网协议族
PF_INET6IPv6互联网协议族
PF_LOCAL本地通信的UNIX协议族
PF_PACKET底层套接字的协议族
PF_IPXIPX Novell协议族

type

  1. 面向连接的套接字(SOCK_STREAM) 【跟TCP一样】

特点:可靠的,按序传递的,基于字节的面向连接的数据传输方式的套接字。

  1. 面向消息的套接字(SOCK_DGRAM) 【跟UDP一样】

特点:不可靠的,不按序传递的,以数据的高速传输为目的的套接字。

protocol

传递前两个参数即可创建所需套接字,第三个参数是为了以下情况:

同一协议族中存在多个数据传输方式相同的的协议。这时需要通过第三个参数具体指定协议信息。

IPv4协议族中面向连接的套接字,协议只有IPPROTO_TCP。

int tcp_socket = socket(PF_INET, SOCK_STREAM, 			      						IPPROTO_TCP);

这个套接字称为TCP套接字。

IPv4协议族中面向消息的套接字,协议只有IPPROTO_UDP

int udp_socket = socket(PF_INET, SOCK_STREAM, 			      						IPPROTO_UDP);

这个套接字称为UDP套接字。

2.bind()
2.1前提
struct sockaddr_in
{sa_family_t		sin_family; //地址族uint16_t		sin_port;   //16位TCP/UDP端口号struct in_addr	sin_addr;	//32位IP地址char			sin_zero[8];//不使用
}struct in_addr
{In_addr_t 	s_addr;		//32位IPv4地址
}

sin_family

地址族含义
AF_INETIPv4网络协议中使用的地址族
AF_INET6IPv6网络协议中使用的地址族
AF_LOCAL本地通信中采用的UNIX协议的地址族

sin_port

它以网络字节序保存。

sin_addr

以网络字节序保存。

sin_zero

无特殊含义。只是为了使结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员。必须填充为0,否则无法得到想要的结果。

 struct sockaddr {sa_family_t  sa_family; //地址族char        sa_data[14]; //地址信息
}

此结构体成员sa_data保存的地址信息中需包含IP地址和端口号,剩余部分应填充0,这也是bind函数要求的。而这对于包含地址信息来讲非常麻烦,继而就有了新的结构体sockaddr_in。若按照之前的讲解填写sockaddr_in结构体,则将生成符合bind函数要求的字节流。最后转换为sockaddr型的结构体变量,再传递给bind函数即可。

2.2字节序与网络字节序

CPU向内存保存数据的方式有2种

  1. 大端序:高位字节存放到低地址。
  2. 小端序:高位字节存放到高位地址。

因为这种情况,所以网络传输时规定了统一的字节序。大端序,这就叫做网络字节序。

传输数据时,先把数据数组转化成大端序格式再进行网络传输。

2.3 字节序转换
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

解释:

  • htons中的h代表主机(host)字节序。
  • htons中的n代表网络(network)字节序。
  • s指的是short
  • l值的是long

htons的含义:把short型数据从主机字节序转化为网络字节序。

ntohs的含义:把short型数据从网络字节序转化成主机字节序。

通常,以s作为后缀的函数中,s代表2个字节shont,因此用于端口号转换;以1作为后缀的函数中,1代表4个字节,因此用于IP地址转换。

注意:除了向sockaddr in结构体变量填充数据外,其他情况无需考虑字节序问题。

2.4 字符串信息转化成网络字节序的整数型
#include <arpa/inet.h>int_addr_t inet_addr(const char* string);
/* 把点分十进制ip地址,转化为32位整型数值,并按照网络字节序*/

成功返回32为整型数值,失败返回INADDR_NONE

同样功能的函数还有:

#include <arpa/inet.h>int inet_aton(const char* string, struct in_addr* addr);

实际编程中若要调用inet_addr函数,需将转换后的IP地址信息代入sockaddr_in结构体中声明的in_addr结构体变量。而inet_aton函数则不需此过程。原因在于,若传递in_addr结构体变量地址值,函数会自动把结果填人该结构体变量。

char* addr = "127.232.124.79";
struct sockaddr_in addr_inet;
inet_acton(addr,&addr_inet.sin_addr);

将32为数值转化为字符串

#include <arpa/inet.h>
char* inet_ntoa(struct in_addr adr);

注意!!!

该函数将通过参数传入的整数型IP地址转换为字符串格式并返回。但调用时需小心,返回值类型为char指针。返回字符串地址意味着字符串已保存到内存空间,但该函数未向程序员要求分配内存,而是在内部申请了内存并保存了字符串。也就是说,调用完该函数后,应立即将字符串信息复制到其他内存空间。因为,若再次调用inet_ntoa函数,则有可能覆盖之前保存的字符串信息。总之,再次调用inet_ntoa函数前返回的字符串地址值是有效的。若需要长期保存,则应将字符串复制到其他内存空间。

2.5 INADDR_ANY

每次创建服务器端套接字都要输入IP地址会有些繁琐,所以采用常数INADDR_ANY分配服务器端的IP地址,可自动获取运行服务器端的计算机IP地址,不必亲自输入。

addr.sin_addr.s_addr = htonl(INADDR_ANY);

2.6 bind()

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
/*
*/
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

成功返回0,失败返回-1

3.listen()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
/*
sockfd : 希望进入等待连接请求状态的套接字文件描述符
backlog:连接请求等待队列的长度。
*/
int listen(int sockfd, int backlog);
4.accept()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
/*
sock:服务器套接字的文件描述符
addr:保存发起连接请求的客户端地址信息的变量地址值,调用函数后向传递来的地址变量参数填充客户端地址信息。
addrlen:第二个参数addr结构体的长度,但是存有长度的变量地址。函数调用完成后,该变量即被填入客户端地址长度。
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
5.connect()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>/*
sockfd:客户端套接字文件描述符
addr:保存目标服务器端地址信息的变量地址值
addrlen:以字节为单位传递地址变量长度
*/
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

注意

客户端的IP地址和端口在调用connect函数时自动分配,无需调用bind函数进行分配。IP用计算机的IP,端口随机。

6.案例小结
6.1服务器端
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cstring>
#include <arpa/inet.h>void error_handling(const char* message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char** argv)
{if(argc != 2){printf("Usage : %s <port> \n",argv[1]);exit(1);}// 创建套接字int serv_sock = socket(PF_INET, SOCK_STREAM, 0);if(serv_sock < 0){error_handling("serv_sock() error");}struct sockaddr_in serv_addr;// 初始化地址memset(&serv_addr,0,sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(atoi(argv[1]));// 分配地址int ret = bind(serv_sock,(struct sockaddr*)&serv_addr, sizeof(serv_addr));if(ret < 0){error_handling("bind() error");}// 开始监听int ret1 = listen(serv_sock, 5);if(ret1 < 0){error_handling("listen() error");}int client_sock;struct sockaddr_in client_addr;socklen_t client_addr_size = sizeof(client_addr);// 接受访问client_sock = accept(serv_sock, (struct sockaddr*) &client_addr, &client_addr_size);if(client_sock < 0){error_handling("accept() error");}char message[] = "hello,world!";// 传输数据write(client_sock, message, sizeof(message));// 传输完成,关闭套接字close(client_sock);close(serv_sock);return 0;
}
6.2 客户端
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>void error_handling(const char* message)
{fputs(message,stderr);fputc('\n',stderr);exit(1);
}int main(int argc, char** argv)
{if(argc != 3){printf("Usage : %s <ip> <port> \n", argv[0]);exit(1);}int sock(-1);// 创建套接字sock = socket(PF_INET, SOCK_STREAM, 0);if(sock < 0){perror("sock()");// error_handling("socket() error");}// 初始化地址和端口号struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]);serv_addr.sin_port = htons(atoi(argv[2]));// 发送请求int ret = connect(sock, (struct sockaddr*)&serv_addr,sizeof(serv_addr));if(ret < 0){perror("connect()");// error_handling("connect() error");exit(1);}char message[30];// 接收数据int str_len = read(sock,message,sizeof(message)-1);if(str_len < 0){perror("read()");// error_handling("read() error");}printf("Message from server : %s \n", message);// 关闭套接字close(sock);return 0;
}
7.半关闭

Linux的close函数意味着完全断开连接。完全断开不仅指无法传输数据,而且也不能接收数据。

开一部分连接是指,可以传输数据但无法接收,或可以接收数据但无法传输。顾名思义就是只关闭流的一半。

#include <sys/socket.h>int shutdown(int sockfd, int how);

how

名称含义
SHUT_RD断开输入流
SHUT_WR断开输出流
SHUT_RDWR同时断开I/O流

2)Window

1. socket()
#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);

这些参数与Linux一样,只是返回值不同。SOCKET其实就是整数类型,微软把他重定义了。

出现错误时,返回INVALID_SOCKET。

#include <winsock2.h>
SOCKET soc = socket(PF_INET, SOCK_STREAM,IPPROTO_TCP);
if(soc == INVALID_SOCKET)errorHandling("........");
2.bind()

与Linux完全相同。但是,Windows中不存在inet aton函数

3.listen()

同Linux

4.accept()

同Linux

5.recv()

同linux的read()

6.send()

同Linux的write()

二、UDP基础知识

1)Linux

1.sendto()
#include <sys/types.h>
#include <sys/socket.h>
/*sockfd : 用于传输数据的UDP套接字文件描述符
buff : 保存待传输数据的换成地址值
nbytes :待传输的数据长度,以字节为单位
flags :可选参数,若没有传递0
to : 存有目标地址信息的sockaddr结构体变量的地址值
addrlen :传递给参数to的地址值结构体变量长度
*/
ssize_t sendto(int sockfd, const void *buf, size_t len, 				int flags,const struct sockaddr 						*dest_addr, socklen_t addrlen);

成功返回传输的字节数,失败返回-1.

调用该函数时自动分配IP和端口号

2.recvfrom()
#include <sys/types.h>
#include <sys/socket.h>
/*
sockfd : 用于接收数据的UDP套接字文件描述符
buff : 保存接收数据的缓冲地址值
nbytes : 可接收的最大字节数,故无法超过参数buff所指的缓冲大小
flags : 可选参数,若没有则传入0
src_addr : 存有发送端地址信息的地址值
addrlen :保存参数from的结构体变量长度的变量地址值
*/
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr,socklen_t *addrlen);
3.已连接UDP套接字

如果对于某个目标,需要一直发送消息,那么就可以使用已连接的UDP。

和TCP套接字一样,使用connect函数,使用这个函数之后,就可以使用write和read函数

4.案例

服务器端

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>const int BUF_SIZE = 30;int main(int argc, char** argv)
{// 判断if(argc != 2){fprintf(stderr,"Usage : %s <port> \n",argv[0]);exit(1);}int serv_sock(-1);// 创建套接字serv_sock = socket(PF_INET,SOCK_DGRAM,0);if(serv_sock < 0){perror("socket()");exit(1);}// 初始化IP地址和端口号struct sockaddr_in sock_addr;memset(&sock_addr, 0, sizeof(sock_addr));sock_addr.sin_family = AF_INET; //地址族sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);//ip地址自动获取本机ipsock_addr.sin_port = htons(atoi(argv[1]));// 分配ip和端口号int ret = bind(serv_sock, (struct sockaddr*)&sock_addr, sizeof(sock_addr));if(ret < 0){perror("bind()");exit(1);}// 初始化客户请求int client_sock(-1);struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);// 保存客户端发来的信息char message[BUF_SIZE];int str_len(0);while(true){// 接收数据str_len = recvfrom(serv_sock, message, BUF_SIZE, 0,(struct sockaddr*)&client_addr, &client_addr_len);// 发送数据sendto(serv_sock, message, str_len, 0,(struct sockaddr*)&client_addr, client_addr_len);}close(serv_sock);return 0;}

客户端

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>const int BUF_SIZE = 30;int main(int argc, char** argv)
{if(argc != 3){printf("Usage : %s <ip> <port> \n", argv[0]);exit(1);}// 创建套接字int clt_sock(-1);clt_sock = socket(PF_INET, SOCK_DGRAM, 0);if(clt_sock < 0){perror("socket()");exit(1);}// 初始化服务器地址struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]);serv_addr.sin_port = htons(atoi(argv[2]));char message[BUF_SIZE];struct sockaddr_in from_addr;memset(&from_addr, 0, sizeof(from_addr));socklen_t from_addr_len = sizeof(from_addr);while (true){fputs("Input message(Q to quit): ",stdout);fgets(message, BUF_SIZE, stdin);if(!strcmp(message,"q\n") || !strcmp(message, "Q\n"))break;// write(clt_sock, message, strlen(message));// 发送数据sendto(clt_sock, message, strlen(message), 0,(struct sockaddr*)&serv_addr, sizeof(serv_addr));// int str_len = read(clt_sock, message, BUF_SIZE -1);// message[str_len] = 0;// 接收数据int len = recvfrom(clt_sock, message, BUF_SIZE, 0,(struct sockaddr*)&from_addr, &from_addr_len);message[len] = 0;printf("Message from server : %s", message);}close(clt_sock);return 0;
}

2)Window

同Linux一模一样。

三、域名

1.由域名得到ip

#include <netdb.h>
extern int h_errno;/* 通过域名获取ip地址*/
struct hostent *gethostbyname(const char *name);struct hostent 
{char  *h_name;            /* official name of host */char **h_aliases;         /* alias list */int    h_addrtype;        /* host address type */int    h_length;          /* length of address */char **h_addr_list;       /* list of addresses */
}

成功返回结构体类型,失败返回NULL

  • h_name :该变量中存有官方域名
  • h_aliases :可以通过多个域名访问同一个主页。
  • h_addrtype :保存地址族信息
  • h_length : 保存IP地址长度
  • h_addr_list : 保存域名1对应的IP地址

2.通过IP地址获取域名

#include <sys/socket.h>       /* for AF_INET */
#include <netdb.h>struct hostent *gethostbyaddr(const void *addr,socklen_t len, int type);
  • addr : 含有IP地址信息的in_addr结构体的指针。
  • len :向第一个参数传递的地址信息的字节数,IPv4时为4,IPv6时为16
  • type:传递地址族信息,IPv4时为AF_INET,IPv6时为IP_INET6

3.Window

同Linux一样,连函数名和参数都相同。

四、套接字的多种选项

1.获取和设置套接字选项

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
  • sockfd :用于查看选项套接字文件描述符
  • Level :要查看的可选项的协议层
  • optname :要查看的可选项名
  • optval :保存查看结果的缓冲地址值
  • optlen :向第四个参数optval传递的缓冲大小。调用函数后,该变量中保存通过第四个参数返回的可选项信息的字节数。

2.各种选项

直接man函数,查看选项。或者上网搜索。

3.Nagle算法

为防止因数据包多过而发生网络过载。

在这里插入图片描述

五、多进程服务端

1.案例

在这里插入图片描述

服务器端

/*** 多进程并发服务器*/
#include <cstdio>
#include <cstdlib>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <cstring>const int BUF_SIZE = 30;
void read_childproc(int sig)//声明信号处理函数
{pid_t pid= waitpid(-1, NULL, WNOHANG);//给子进程收尸printf("remove proc id : %d\n", pid); 
}int main(int argc, char** argv)
{if(argc != 2){fprintf(stderr,"Usage : %s <port>\n",argv[0]);return 1;}// 设置子进程结束信号struct sigaction act;act.sa_handler = read_childproc;//设置信号处理函数sigemptyset(&act.sa_mask);//置0act.sa_flags = 0;sigaction(SIGCHLD, &act, NULL);//设置信号// 创建socketint serv_sock(-1);serv_sock = socket(PF_INET, SOCK_STREAM, 0);if(serv_sock < 0){perror("socket()");return 1;}// 初始化ip,portstruct sockaddr_in serv_add;memset(&serv_add, 0, sizeof(serv_add));serv_add.sin_family = AF_INET;//IPv4协议族serv_add.sin_addr.s_addr = htonl(INADDR_ANY);//自动获取本机ipserv_add.sin_port = htons(atoi(argv[1]));//分配ip,portint ret = bind(serv_sock, (struct sockaddr*)&serv_add, sizeof(serv_add));if(ret < 0){perror("bind()");return 1;}//进入请求等待 int ret0 = listen(serv_sock,5);if(ret0 < 0){perror("listen()");return 1;}int clnt_sock(-1);//用来保存客户端套接字struct sockaddr_in clnt_addr;//用来保存客户端地址socklen_t clnt_size = sizeof(clnt_addr);while(true){//接收客户端请求clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_addr, &clnt_size);if(clnt_sock < 0){continue;}elseputs("new client connection....");pid_t pid;pid = fork();//创建子进程if(pid < 0)//error{close(clnt_sock);continue;}else if(pid ==0)//child{close(serv_sock);//int str_len(0);char message[BUF_SIZE];while((str_len = read(clnt_sock,message,BUF_SIZE)) != 0){write(clnt_sock, message, str_len);}close(clnt_sock);puts("client disconnected....");return 0;}else //父进程{close(clnt_sock);//从队列中删除}}close(serv_sock);return 0;
}

客户端

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>const int BUF_SIZE = 1024;int main(int argc, char** argv)
{if(argc != 3){printf("Usage : %s <ip> <port> \n", argv[0]);exit(1);}// 创建套接字int clt_sock(-1);clt_sock = socket(PF_INET, SOCK_STREAM, 0);if(clt_sock < 0){perror("socket()");exit(1);}// 初始化服务器地址struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]);serv_addr.sin_port = htons(atoi(argv[2]));// 发送请求int ret = connect(clt_sock,(struct sockaddr*)&serv_addr, sizeof(serv_addr));if(ret < 0){perror("connect()");exit(1);}else{puts("Connected.......");}char message[BUF_SIZE];while (true){fputs("Input message(Q to quit): ",stdout);fgets(message, BUF_SIZE, stdin);if(!strcmp(message,"q\n") || !strcmp(message, "Q\n"))break;write(clt_sock, message, strlen(message));int str_len = read(clt_sock, message, BUF_SIZE -1);message[str_len] = 0;printf("Message from server : %s", message);}close(clt_sock);return 0;
}

2.分割I/O

在这里插入图片描述

服务器端如1一样。

客户端

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>const int BUF_SIZE = 30;void read_routine(int sock, char* buf)
{while(true){int str_len = read(sock, buf, BUF_SIZE);if(str_len == 0)return ;buf[str_len] = 0;printf("<Message from server> : %s", buf);}}void write_routine(int sock, char* buf)
{while(1){fgets(buf, BUF_SIZE, stdin);if(!strcmp(buf, "q\n") || !strcmp(buf,"Q\n")){shutdown(sock, SHUT_WR);//关闭输出流,但是不关闭输入流return;}write(sock, buf, strlen(buf));}
}int main(int argc, char** argv)
{if(argc != 3){printf("Usage : %s <ip> <port> \n", argv[0]);exit(1);}// 创建套接字int clt_sock(-1);clt_sock = socket(PF_INET, SOCK_STREAM, 0);if(clt_sock < 0){perror("socket()");exit(1);}// 初始化服务器地址struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]);serv_addr.sin_port = htons(atoi(argv[2]));// 发送请求int ret = connect(clt_sock,(struct sockaddr*)&serv_addr, sizeof(serv_addr));if(ret < 0){perror("connect()");exit(1);}else{puts("Connected.......");}char message[BUF_SIZE];// 创建子进程pid_t pid;pid = fork();if(pid < 0)// error{perror("fork()");return 1;}else if (pid == 0) //child{write_routine(clt_sock, message);}else{read_routine(clt_sock, message);}close(clt_sock);return 0;
}

六、进程间通信

1.基于管道(PIPE)的通信

#include <unistd.h>int pipe(int pipefd[2]);

成功返回0,失败返回-1.

  • pipefd[0] : 通过管道接收数据时使用的文件描述符,即管道出口。
  • pipefd[1] :通过管道传输数据时使用的文件描述,即管道入口。
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <string.h>
#include <wait.h>const int BUF_SIZE = 40;int main()
{int fd[2];int ret = pipe(fd);if(ret < 0){perror("pipe()");return 1;}pid_t pid;char message[BUF_SIZE];pid = fork();if(pid < 0){perror("fork()");return 1;}else if(pid == 0){//    sleep(4);int str_len = read(fd[0],message,BUF_SIZE);message[str_len] = 0;printf("child read : %s\n", message);close(fd[0]);close(fd[1]);  }else{// printf("in :\n");// fputs(message, stdin);char msg[] = "who are you ?";write(fd[1], msg, strlen(msg));close(fd[0]);close(fd[1]);wait(NULL);}return 0;
}

七、并发服务器

1.I/O复用

1.1 select()

该函数,移植性比较好。

#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);struct timeval
{long    tv_sec;         /* seconds */long    tv_usec;        /* microseconds */
};
1.2.epoll()

这个函数仅仅适用于Linux,

linux的方言,不可以移植。

#include <sys/epoll.h>
/*
创建一个epoll描述符。
size 可以随便填,给个正数即可
*/
int epoll_create(int size);
int epoll_create1(int flags);

epoll_ctl()可以向指定的 epoll 上下文中加入或删除文件描述符:

#include <sys/epoll.h>
/* 管理一个epoll描述符 */
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

op 的选项

名称描述
EPOLL_CTL_ADD把fd指定的文件添加到epfd指定的epoll实例监听集中
EPOLL_CTL_DEL把fd指定的文件从epfd指定的epoll监听集中删掉
EPOLL_CTL_MOD使用event改变在已有fd上的监听行为
typedef union epoll_data
{void        *ptr;int          fd;uint32_t     u32;uint64_t     u64;
} epoll_data_t;struct epoll_event 
{ uint32_t     events;      /* Epoll events */epoll_data_t data;        /* User data variable */
};

events参数

名称描述
EPOLLERR文件出错。即使没有设置,这个事件也是被监听的
EPOLLET在监听文件上开启边沿触发,默认行为是水平触发
EPOLLHUP文件被挂起,即使没有设置,这个事件也是被监听的
EPOLLIN文件未阻塞,可读
EPOLLONESHOT在一次事件产生并处理后,文件不再被监听。(必须指定新事件)
EPOLLOUT文件未阻塞,可写
EPOLLPRI高优先级的带外数据可读
#include <sys/epoll.h>/* wait for an I/O event on an epoll file descriptor */
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

如果 timeout 为0,即使没有事件发生,调用也立即返回,此时调用返回0。如果 timeout 为 -1,调用将一直等待到有事件发生。
当调用返回,epoll_event 结构体中的 events 字段描述了发生的事件。 data字段包含了所有用户在调用 epoll_ctl()前的设置。

1.2.2 案例
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <cstring>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/epoll.h>const int BUF_SIZE = 100;int main(int argc, char** argv)
{if(argc != 2){printf("Usage %s <port> \n",argv[0]);return 1;}int sock_serv;// 创建套接字sock_serv = socket(PF_INET, SOCK_STREAM, 0);if(sock_serv < 0){perror("socket()");return 1;}// ip,portstruct sockaddr_in serv_add;memset(&serv_add, 0, sizeof(serv_add));serv_add.sin_family = AF_INET;serv_add.sin_addr.s_addr = htonl(INADDR_ANY);serv_add.sin_port = htons(atoi(argv[1]));int ret = bind(sock_serv,(struct sockaddr*)&serv_add, sizeof(serv_add));if(ret < 0){perror("bind()");return 1;}int ret1 = listen(sock_serv, 5);if(ret1 < 0){perror("listen()");return 1;}int sock_clnt;// struct timeval mytime;// fd_set readset;// FD_ZERO(&readset);// 把服务器套接字放入监视集合中// FD_SET(sock_serv,&readset);// int fd_max = sock_serv;// fd_set copy_readset;struct sockaddr_in clnt_addr;// 创建epollint epfd = epoll_create(5);// 设置事件struct epoll_event* epevent;//保存返回的事件struct epoll_event event;//保存一开始的事件event.events = EPOLLIN;event.data.fd = sock_serv;// 给epoll添加描述符和事件epoll_ctl(epfd, EPOLL_CTL_ADD, sock_serv, &event);//初始化epevent =(struct epoll_event*) malloc(sizeof(epevent)*5);char message[BUF_SIZE];while(true){// copy_readset = readset;// mytime.tv_sec = 5;// mytime.tv_usec = 5000;// int ans = select(fd_max + 1, &copy_readset, NULL, NULL, &mytime);int epoll_count = epoll_wait(epfd, epevent, 5, -1);if(epoll_count < 0){perror("epoll_wait()");break;}else if(epoll_count == 0){// printf("time over \n");continue;}else {for(int i = 0; i < epoll_count; ++i){// if(FD_ISSET(i, &copy_readset))if(epevent[i].data.fd == sock_serv){// if(i == sock_serv)//sock_serv有请求// {socklen_t clnt_size = sizeof(clnt_addr);sock_clnt = accept(sock_serv, (struct sockaddr*)&sock_clnt,&clnt_size);if(sock_clnt < 0){continue;}// FD_SET(sock_clnt,&readset);// if(fd_max < sock_clnt)// {//     fd_max = sock_clnt;// }struct epoll_event clnt_event;clnt_event.data.fd = sock_clnt;clnt_event.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, sock_clnt, &clnt_event);printf("Client %d is connecting...\n", sock_clnt);}else{// int str_len = read(i, message, BUF_SIZE);int str_len = read(epevent[i].data.fd, message, BUF_SIZE);if(str_len == 0) //关闭请求{// FD_CLR(i, &copy_readset);epoll_ctl(epfd, EPOLL_CTL_DEL, sock_clnt,NULL);close(i);printf("close client: %d \n", i);}else{write(epevent[i].data.fd, message, str_len);}}}}}close(epfd);close(sock_serv);   return 0;
}
1.2.3边沿触发与条件触发

条件触发方式中,只要输入缓冲有数据就会一直通知该事件。

边缘触发中输入缓冲收到数据时仅注册1次该事件。即使输入缓冲中还留有数据,也不会再注册。边缘触发方式下,以阻塞方式工作的read和write函数有可能引起服务器端的长时间停顿。因此,边缘触发方式中一定要采用非阻塞read和write函数。

将套接字改为非阻塞方式的方法

#include <unistd.h>
#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );
名称含义
F_GETFL获得fd的文件描述符属性
F_SETFL更改文件描述符属性

将文件改为非阻塞模式

int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);

2.多进程实现并发

看五、多进程服务

3.多线程服务器端

3.1 创建线程
#include <pthread.h>
/*
pthread_t* 传入一个该类型的地址
pthread_attr_t 设置线程的属性
第三个参数:传一个函数,返回类型为void*,参数为void*,
第四个参数:为函数传入参数
*/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);//Compile and link with -pthread.
3.2 等待线程的消亡
#include <pthread.h>/*
thread : 线程号
retval : 保存线程函数的返回值。
*/
int pthread_join(pthread_t thread, void **retval);
3. 3 互斥量
#include <pthread.h>pthread_mutex_t;int pthread_mutex_destroy(pthread_mutex_t *mutex);//第二个参数为锁的属性,可以为NULL
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
#include <pthread.h>int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
3.4 信号量
#include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);int sem_destroy(sem_t *sem);
//Link with -pthread.
  • sem : 创建信号量时传递保存信号量的变量地址值。
  • pshared :创建可由多个进程共享的信号量,传递0,创建只允许1个进程内部使用的信号量。
  • value :指定新创建的信号量初始值。
int sem_post(sem_t *sem);int sem_wait(sem_t *sem);int sem_trywait(sem_t *sem);

使用模板

sem_wait(&sem);
//临界区的开始
.....
//临界区的结束
sem_post(&se)

八、多种I/O函数

1.send(),recv()

#include <sys/types.h>
#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);ssize_t recv(int sockfd, void *buf, size_t len, int flags);

flags

名称含义
MSG_OOB用于传输带外数据(发送紧急消息)
MSG_PEEK验证输入缓冲中是否存在接收的数据
MSG_DONTROUTE数据传输过程中不参照路由表在本地网络中寻找目的地
MSG_DONTWAIT调用I/O函数时不堵塞,用于非堵塞I/O
MSG_WITALL防止函数返回,直到接收全部请求的字节数

2.readv(),writev()

也就是说,通过writev函数可以将分散保存在多个缓冲中的数据一并发送,通过readv函数可以由多个缓冲分别接收。因此,适当使用这2个函数可以减少I/0函数的调用次数。

#include <sys/uio.h>ssize_t readv(int fd, const struct iovec *iov, int iovcnt);ssize_t writev(int fd, const struct iovec *iov,int iovcnt);struct iovec 
{void  *iov_base;    /* Starting address */size_t iov_len;     /* Number of bytes to transfer */
};

九、多播和广播

是基于UDP套接字实现。

1.设置TTL

在这里插入图片描述

2.加入多播组

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.广播

在这里插入图片描述

十、Linux编程

1.标准IO与系统IO之间的相互转化

#include <stdio.h>//由fd转化为FILE指针类型
FILE *fdopen(int fd, const char *mode);//由 FILE指针类型转化为fd
int fileno(FILE *stream)

2.复制文件描述符dup

#include <unistd.h>int dup(int oldfd);
int dup2(int oldfd, int newfd);

3.实现半关闭

fd转化为FILE类型指针图。

在这里插入图片描述

通过复制文件描述符,实现半关闭。

在这里插入图片描述

在这里插入图片描述

调用shutdown,发送EOF,实现半关闭。

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

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

相关文章

Idea不能创建java8切换路径

顶部的Server URL改成https://start.aliyun.com/

【原创】可用于 Android Studio 的翻译插件

在不少讲解Android 开发的老师视频中会出现一个运行在Android Studio 上的翻译插件&#xff0c;感觉挺实用的。 接下来&#xff0c;我们把它安装在我们的Android Studio 上。 设置 点击右上角齿轮按钮&#xff0c;选择Settings 安装 翻译插件 输入Tanslation&#xff0c;选…

ZStack ZROP首个商用版本发布,打造云的可持续发展框架

经过长时间的研发和测试&#xff0c;ZStack ZROP IT服务中台V4.2.0版本正式发布。ZROP 是针对ZStack全系列产品运营、运维、一体化的自研平台。作为第一个商用版本&#xff0c;ZROP V4.2.0支持包含ZStack Cloud、ZStack Cube、ZStack ZStone、ZStack Zaku、ZStack Edge、ZStack…

针对考研的C语言学习(循环队列-链表版本以及2019循环队列大题)

题目 【注】此版本严格按照数字版循环队列的写法&#xff0c;rear所代表的永远是空数据 图解 1.初始化部分和插入部分 2出队 3.分部代码解析 初始化 void init_cir_link_que(CirLinkQue& q) {q.rear q.front (LinkList)malloc(sizeof(LNode));q.front->next NULL…

C++的随机数操作

首先想到的肯定是rand()函数&#xff0c;但是这个有点问题 引入头文件<stdlib.h> 如果不引入种子&#xff0c;它的随机数不是随机数&#xff0c;是固定的一串数字 srand()函数&#xff0c;产生随机的种子 示例&#xff1a; 产生0-99的随机数 #include<stdlib.h&g…

QD1-P5 HTML 段落标签(p)换行标签(br)

本节视频 www.bilibili.com/video/BV1n64y1U7oj?p5 ‍ 本节学习 HTML 标签&#xff1a; p标签 段落br标签 换行 ‍ 一、p 标签-段落 1.1 使用 p 标签划分段落 <p>段落文本</p>示例 <!DOCTYPE html> <html><head><meta charset"…

谷歌浏览器 文件下载提示网络错误

情况描述&#xff1a; 谷歌版本&#xff1a;129.0.6668.90 (正式版本) &#xff08;64 位&#xff09; (cohort: Control)其他浏览器&#xff0c;比如火狐没有问题&#xff0c;但是谷歌会下载失败&#xff0c;故推断为谷歌浏览器导致的问题小文件比如1、2M会成功&#xff0c;大…

第十五届蓝桥杯C++B组省赛

文章目录 1.握手问题解题思路1&#xff08;组合数学&#xff09;解题思路2&#xff08;暴力枚举&#xff09; 2.小球反弹做题思路 3.好数算法思路&#xff08;暴力解法&#xff09;---不会超时 4.R格式算法思路 5.宝石组合算法思路---唯一分解定理 6.数字接龙算法思路----DFS 7…

GO网络编程(五):海量用户通信系统3:整体框架与C/S通信总体流程【重要】

这个系统其实是尚硅谷的老韩讲的&#xff08;尚硅谷网络编程项目&#xff09;&#xff0c;但是他讲得很碎片化&#xff0c;思路不够清晰&#xff0c;时间又长&#xff0c;所以要掌握还是挺难的。如果你听了他的视频&#xff0c;不去梳理系统业务流程&#xff0c;不去看代码就往…

专题十一_递归_回溯_剪枝_综合练习_算法专题详细总结

目录 1. 找出所有⼦集的异或总和再求和&#xff08;easy&#xff09; 解析&#xff1a; 方法一&#xff1a; 解法二&#xff1a; 总结&#xff1a; 2. 全排列 Ⅱ&#xff08;medium&#xff09; 解析&#xff1a; 解法一&#xff1a;只关心“不合法”的分支 解法二&…

关于Linux下C++程序内存dump的分析和工具

前言 程序崩溃令人很崩溃&#xff0c;特别是让人找不到原因的崩溃&#xff0c;但是合适的工具可以帮助人很快的定位到问题&#xff0c;在AI基础能力ASR服务开发时&#xff0c;找到了一种比较实用和简单的内存崩溃的dump分析工具breakpad&#xff0c; 可以帮助在Linux下C开发程…

QD1-P8 HTML 格式化标签(font、pre、b、strong、i、u、del、s、sub、sup)

本节学习&#xff1a;HTML 格式化标签。 本节视频 www.bilibili.com/video/BV1n64y1U7oj?p8 ‍ 一、font 标签 用途&#xff1a;定义文本的字体大小、颜色和 face&#xff08;字体类型&#xff09;。 示例 <!DOCTYPE html> <html><head><meta cha…

难点:Linux 死机定位(进程虚拟地址空间耗尽)

死机定位(进程虚拟地址空间耗尽) 一、死机现象 内存富裕,但内存申请失败。 死机时打印: 怀疑是: 1、内存碎片原因导致。 2、进程虚拟地址空间耗尽导致。 3、进程资源限制导致。 二、内存碎片分析 1、理论知识:如何分析内存碎片化情况 使用 /proc/buddyinfo: /proc/…

CCF推荐被调查,这8本被标记On Hold

近两年“On Hold”期刊频出&#xff0c;作为投稿选刊风向标&#xff0c;上榜期刊一定要避雷投稿&#xff01;本期科检易学术小编盘点目前被科睿唯安官方标记为“On Hold”的计算机工程类的8本期刊&#xff0c;提醒广大学者&#xff0c;选刊需谨慎&#xff0c;注意避雷哦&#x…

Spark高级用法-内置函数

目录 读取数据 1.字符串 2.数值类 3.时间类型 4.条件判断 5.窗口函数 读取数据 # 内置数据集 from pyspark.sql import SparkSession,functions as F ss SparkSession.builder.getOrCreate()# 读取文件准尉df df ss.read.csv(hdfs://node1:8020/data/students.csv,hea…

【uniapp】使用uniapp实现一个输入英文单词翻译组件

目录 1、组件代码 2、组件代码 3、调用页面 4、展示 前言&#xff1a;使用uniapp调用一个在线单词翻译功能 1、组件代码 2、组件代码 YouDaoWordTranslator <template><view class"translator"><input class"ipttext" type"te…

JVM 内存模型与垃圾回收过程详解

JVM 内存模型与垃圾回收过程详解 文章目录 JVM 内存模型与垃圾回收过程详解1. JVM内存分区1.1 具体分区1.2 JVM内存分区的必要性 2. 垃圾回收2.1 CMS垃圾回收器2.2 G1垃圾回收器2.3 JVM垃圾回收从新生代到老年代 1. JVM内存分区 1.1 具体分区 Java虚拟机&#xff08;JVM&#…

计算机毕业设计 内蒙古旅游景点数据分析系统的设计与实现 Python毕业设计 Python毕业设计选题 Spark 大数据【附源码+安装调试】

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

06-ArcGIS For JavaScript-requestAnimationFrame动画渲染

文章目录 概述setInterval&#xff08;&#xff09;与setTimeout()requestAnimationFrame()requestAnimationFrame在ArcGIS For JavaScript的应用结果 概述 本节主要讲解与时间相关的三个方法setTimeout()、setInterval()和requestAnimationFrame()&#xff0c;这三个方法都属…

Java每日面试题(集合)(day18)

目录 常见的集合有哪些&#xff1f;Collection和Collections有什么区别&#xff1f;ArrayList 和 Array&#xff08;数组&#xff09;的区别&#xff1f;ArrayList 和 LinkedList 的区别是什么&#xff1f;Arraylist 和 Vector 的区别HashMap和Hashtable的区别哪些集合类是线程…