1.socket函数
为了执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型
#include <sys/socket.h> int socket (int family, int type, int protocol); //返回:若成功则为非负描述符,若出错则为-1
其中family指明协议族,type参数指明套接字类型,protocol参数应该设为某个(见下图)协议类型常值,或者设为0,以选择所给定family和type组合的系统默认值
socket函数的family常值
family | 说 明 |
AF_INET AF_INET6 AF_LOCAL AF_ROUTE AF_KEY |
IPv4协议 IPv6协议 Unix域协议 路由套接口 密钥套接口 |
socket函数的type常值
type | 说 明 |
SOCK_STREAM SOCK_DGRAM SOCK_SEQPACKET SOCK_RAW |
字节流套接口 数据报套接口 有序分组套接口 原始套接口 |
socket函数的protocol常值
protocol | 说 明 |
IPPROTO_TCP IPPROTO_UDP IPPROTO_SCTP |
TCP传输协议 UDP传输协议 SCTP传输协议 |
socket函数中family和type参数的组合
2.connect函数
TCP客户用connect函数来建立与TCP服务器的连接
#include <sys/socket.h> int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); //返回:若成功则为0,若出错则为-1
sockfd是socket函数返回的套接字描述符,剩下的2个参数分别是一个指向套接字地址结构的指针和该结构的大小。connect函数将激发TCP的三次握手过程,而且仅在连接建立成功或出错时才返回,其中出错有如下几种情况:
1).若TCP客户没有收到SYN包的响应,则返回ETIMEDOUT错误。如调用该函数时,内核发送一个SYN,若无响应则等待6s后再发一个,若仍无响应,则等待24s再发一个,若总共等了75s后仍未收到响应消息则返回该错误(因内核而异)。
2).若响应时RST,表明该服务器主机在我们指定的端口上没有进程等待,客户收到RST包后马上返回ECONNREFUSED错误。
3).若客户发出的SYN在中间的路由器上引发了一个“destination unreachable”的ICMP错误,则按第一种情形继续发送SYN,若在规定的时间内没有收到回应,则将ICMP错误作为EHOSTUNREACH或ENETUNREACH错误返回。
3.bind函数
bind函数把一个本地协议地址赋予一个套接字。对于网际协议,协议地址是一个ip地址和一个端口号
#include <sys/socket.h> int bind (int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); //返回,成功为0,出错为-1
参数sockfd是socket函数返回的套接字描述符,myaddr是一个指向特定于协议的地址结构的指针,第三个参数是该地址结构的长度,对于TCP,调用bind函数可以指定一个端口,或者指定一个地址,也可以两者都指定,还可以都不指定:
- 服务器在启动时候捆绑他们众所周知的端口
- 进程可以把一个特定的IP地址捆绑到它的套接字上,不过这个IP地址必须属于其所在主机的网络接口之一
其中对于IPv4来说,通配地址常值INADDR_ANY来指定,其值一般为0,它通知内核选择IP地址
4.listen函数
函数listen 仅被TCP服务器调用,它做两件事件:
1).当函数socket创建一个套接口时,它被假设为一个主动套接口,也就是说,它是一个将调用connect发起连接的客户套接口,函数listen将未连接的套接口转换成被动套接口,指示内核应接受指向此套接口的连接请求,
2).函数的第二个参数规定了内核为此套接口排队的最大连接个数
#include <sys/socket.h> int listen (int sockfd, int backlog); //返回,成功为0,出错-1
要理解backlog参数,我们要知道内核为任何一个给定的监听套接字维护2个队列:
1).未完成连接队列。客户和服务器之间的tcp三次握手并未完成。
2).已完成连接队列。tcp的三次握手已经完成,处于ESTABLISHED状态。
关于两个队列的处理:
- listen函数的backlog参数曾被规定为两个队列总和的最大值
- 源自Berkeley的实现给backlog增设了一个模糊因子,把它乘以1.5得到未处理队列最大长度
- 不要把backlog定义为0,因为不同的实现对此有不同的解释
- 在三路握手正常完成的前提下(也就是说没有丢失分节,从而没有重传),未完成连接队列的任何一项在其中的存留时间就是一个RTT,而RTT的值取决于特定的客户与服务器
- 当一个客户SYN到达时,若这些队列是满的,TCP就忽略该分节,也就是不发送RST
- 在三路握手完成后,但在服务器调用accept之前到达的数据应由服务器TCP排列,最大数据量为相应已连接套接字的接受缓冲区大小
5.accept函数
accept函数由TCP服务器调用,用于从已完成连接队列列头返回下一个已完成连接,如果已完成连接队列为空,进程将被投入睡眠(如果套接字为默认的阻塞方式)
#include <sys/socket.h> int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); //返回:若成功为非负描述符,出错为-1
参数cliaddr和addrlen返回已连接的客户的协议地址,如果对客户的协议地址不感兴趣,可以置为空,参数addrlen在函数调用的时候是传入的套接字地址结构的大小,函数返回时它的值是内核存放在该套接字地址结构中的确切字节数。
如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与返回客户的TCP连接,一般我们称accept函数第一个参数为监听套接字描述符(由socket创建,随后用作bind和listen的第一个参数的描述符),称它的返回值为已连接套接字描述符
accept 函数最多返回三个值:一个既可能是新的套接字描述符也可能是出错指示的整数、客户进程的协议地址(由cliaddr指针所指)、以及该地址的大小(由addrlen指针所指)。
TCP cs模型
server.c
#include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #define SERVER_PORT 5555 #define MAXLINE 4096 int main(void) { struct sockaddr_in serveraddr, clientaddr; int sockfd, addrlen, confd, len, i; char ipstr[128]; char buf[MAXLINE]; //1.socket sockfd = socket(AF_INET, SOCK_STREAM, 0); //2.bind bzero(&serveraddr, sizeof(serveraddr)); /* 地址族协议IPv4 */ serveraddr.sin_family = AF_INET; /* IP地址 */ serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons(SERVER_PORT); bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); //3.listen listen(sockfd, 128); while (1) { //4.accept阻塞监听客户端链接请求 addrlen = sizeof(clientaddr); confd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen); //输出客户端IP地址和端口号 printf("client ip %s\tport %d\n", inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, ipstr, sizeof(ipstr)), ntohs(clientaddr.sin_port)); //和客户端交互数据操作confd //5.处理客户端请求 len = read(confd, buf, sizeof(buf)); i = 0; while (i < len) { buf[i] = toupper(buf[i]); i++; } write(confd, buf, len); close(confd); } close(sockfd); return 0; }
client.c
#include <netinet/in.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #define SERVER_PORT 8000 #define MAXLINE 4096 int main(int argc, char *argv[]) { struct sockaddr_in serveraddr; int confd, len; char ipstr[] = "192.168.6.254"; char buf[MAXLINE]; if (argc < 2) { printf("./client str\n"); exit(1); } //1.创建一个socket confd = socket(AF_INET, SOCK_STREAM, 0); //2.初始化服务器地址 bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; //"192.168.6.254" inet_pton(AF_INET, ipstr, &serveraddr.sin_addr.s_addr); serveraddr.sin_port = htons(SERVER_PORT); //3.链接服务器 connect(confd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); //4.请求服务器处理数据 write(confd, argv[1], strlen(argv[1])); len = read(confd, buf, sizeof(buf)); write(STDOUT_FILENO, buf, len); //5.关闭socket close(confd); return 0; }
Makefile
all:server client server:server.c gcc $< -o [email protected] client:client.c gcc $< -o [email protected] .PHONY:clean clean: rm -f server rm -f client
6.数据报接受与发送
#include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen); ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t addrlen); //均返回:若成功则为读或写的字节数,出错为-1 默认情况recvfrom函数没有接收到对方数据时候是阻塞的
UDP cs模型
server.c
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <netinet/in.h> #include <ctype.h> #define SERVER_PORT 5556 #define MAXLINE 1024 int main(void) { int sockfd,i; ssize_t len; struct sockaddr_in serveraddr,clientaddr; char buf[MAXLINE]; char ipstr[INET_ADDRSTRLEN];//16个字节 socklen_t clientlen; //构造用于UDP通信的套接字 sockfd=socket(AF_INET,SOCK_DGRAM,0); bzero(&serveraddr,sizeof(serveraddr)); serveraddr.sin_family =AF_INET; serveraddr.sin_addr.s_addr=htonl(INADDR_ANY); serveraddr.sin_port=htons(SERVER_PORT); //printf("%x\n",INADDR_ANY); bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)); while(1){ clientlen=sizeof(clientaddr); len=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&clientaddr,&clientlen); printf("client IP %s\t PORT %d\n", inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ipstr,sizeof(ipstr)), ntohs(clientaddr.sin_port)); i=0; while(i<len){ buf[i]=toupper(buf[i]); i++; } buf[i]=‘\0‘; sendto(sockfd,buf,len,0,(struct sockaddr*)&clientaddr,sizeof(clientaddr)); } close(sockfd); return 0; }
client.c
#include <netinet/in.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <ctype.h> #define SERVER_PORT 5556 #define MAXLINE 4096 int main(int argc,char *argv[]) { struct sockaddr_in serveraddr; int confd; ssize_t len; char ipstr[]="123.206.59.138"; char buf[MAXLINE]; if(argc <2){ printf("./client str\n"); exit(1); } //1.创建一个socket confd=socket(AF_INET,SOCK_DGRAM,0); //2.初始化服务器地址 bzero(&serveraddr,sizeof(serveraddr)); serveraddr.sin_family=AF_INET; // inet_pton(AF_INET,ipstr,&serveraddr.sin_addr.s_addr); serveraddr.sin_port =htons(SERVER_PORT); //3向务器发送数据 sendto(confd,argv[1],strlen(argv[1]),0,(struct sockaddr *)&serveraddr,sizeof(serveraddr)); len=recvfrom(confd,buf,sizeof(buf),0,NULL,0); write(STDIN_FILENO,buf,len); close(confd); return 0; }