自己做电影网站犯法吗图片链接怎么生成
文章目录
- 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_INET | IPv4互联网协议族 | 
| PF_INET6 | IPv6互联网协议族 | 
| PF_LOCAL | 本地通信的UNIX协议族 | 
| PF_PACKET | 底层套接字的协议族 | 
| PF_IPX | IPX Novell协议族 | 
type
- 面向连接的套接字(SOCK_STREAM) 【跟TCP一样】
 
特点:可靠的,按序传递的,基于字节的面向连接的数据传输方式的套接字。
- 面向消息的套接字(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_INET | IPv4网络协议中使用的地址族 | 
| AF_INET6 | IPv6网络协议中使用的地址族 | 
| 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种
- 大端序:高位字节存放到低地址。
 - 小端序:高位字节存放到高位地址。
 
因为这种情况,所以网络传输时规定了统一的字节序。大端序,这就叫做网络字节序。
传输数据时,先把数据数组转化成大端序格式再进行网络传输。
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, ©_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, ©_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, ©_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,实现半关闭。
