1.套接字的地址结构:
1 typedef uint32_t in_addr_t; //32位无符号整数,用于表示网络地址 2 struct in_addr{ 3 in_addr_t s_addr; //32位 ipv4 地址 4 } 5 typedef uint16_t in_port_t; //16位无符号整数,用于表示端口号 6 struct sockaddr_in{ 7 uint8_t sin_len; //结构长度,8位无符号整数 8 sa_family_t sin_family; //套接字地址族 9 in_port_t sin_port; //16位 TCP 或 UDP 端口号 10 struct in_addr sin_addr; //32位 ipv4 地址 11 char sin_zero[8]; //暂时不用。总置为0 12 }
2.创建套接字函数 socket
int socket(int family , int type , int protocol)
- family : 说明网络程序采用的通信协议族(比如 AF_INET 对应于 TCP/IP 协议族)
- type : 网络程序所采用的通信协议(SOCKET_STREAM 表示创建 TCP 协议套接字,SOCK_DGRAM 表示创建 UDP 套接字,SOCK_RAW 表示创建原始套接字)
- protocol : 由于指定了 type ,所以这里一般用 0 来代替就可以了
- socket 函数成功时返回套接字描述符,失败时返回 -1
3.绑定函数 bind
int bind(int sockfd , struct sockaddr *my_addr , int addrlen)
- sockfd : 由 socket调用 返回的套接字描述符
- my_addr : 一个指向与协议对应的地址结构的指针。使用时需要讲指向特定协议地址结构的指针转换位指向 sockaddr 类型的指针。
- addrlen : sockaddr结构的长度
- bind 函数成功时返回0,失败时返回-1
4.监听函数 listen
int listen(int sockfd,int backlog)
- sockfd : 绑定后的套接字描述符
- backlog : 设置请求排队的最大长度。当由多个客户端请求和服务器连接时,该参数表示可以接收的排队长度
- listen函数调用成功返回0,失败返回-1
5.接受函数 accept
int accept(int sockfd , struct sockaddr *cliaddr , int *addrlen)
- accept仅被 tcp 服务器调用。它从已完成连接的队列头返回下一个已完成的连接,若已完成连接的队列为空,则进入睡眠状态。
- sockfd : 执行监听(listen)之后的套接字描述符。
- client : 返回连接对方的套接字地址结构
- addrlen : 返回对方套接字地址结构(client)的长度
- accept 函数成功执行后会返回一个全新的描述符,代表与客户端的 tcp 连接。若失败则返回 -1
注意:监听套接字和已连接套接字是不同的两个概念。一个给定的服务器通常只会生成一个监听套接字并且一直存在,直到该服务器关闭。内核会为每个被接受的客户连接创建一个已连接套接字,当服务器完成某个客户的服务时,关闭该已连接套接字。监听描述符负责接收客户的连接请求,而已连接描述符负责与对应的客户进行数据传输。
6.连接函数 connect
int connect(int sockfd , struct sockaddr * serv_addr , int addrlen)
- tcp 客户端通过 connect 函数来建立一个与 tcp 服务器的连接
- sockfd : 套接字描述符
- serv_addr : 指向服务器套接字地址结构的指针,套接字地址结构必须含有服务器的 ip 地址和端口号
- addrlen : serv_addr的长度
7.连接中止函数close
int close(int sockfd) 关闭套接字
下面是完整的 服务端 代码:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<errno.h> 4 #include<string.h> 5 #include<netinet/in.h> 6 #include<arpa/inet.h> 7 #include<unistd.h> 8 9 int main() 10 { 11 /*定义套接字描述符,客户和服务器的套接字地址变量*/ 12 int sockfd , new_fd; //定义监听套接字和已连接套接字 13 struct sockaddr_in server; //服务端地址和其他信息 14 struct sockaddr_in client; //客户端地址和其他信息 15 int sin_size , port = 5050; //定义端口号 16 17 /*服务器端开始建立 socket 描述符*/ 18 if((sockfd = socket(AF_INET , SOCK_STREAM , 0))==-1) 19 { 20 printf("Socket error : %s\n",strerror(errno)); 21 exit(1); 22 } 23 /*设置地址重用选项。由于系统默认只允许一个套接字绑在一个特定的协议地址上,并且当套接字关闭后系统仍不允许在该地址上绑定其他套接字。*/ 24 int opt = SO_REUSEADDR; 25 setsockopt(sockfd , SOL_SOCKET , SO_REUSEADDR , &opt , sizeof(opt)); 26 /*套接字绑定特定地址和端口,进入监听状态*/ 27 bzero(&server , sizeof(struct sockaddr_in)); //将字节字符串前 n 个字节置0 28 server.sin_family = AF_INET; 29 server.sin_addr.s_addr = htonl(INADDR_ANY); 30 server_addr.sin_port = htons(port); //填充套接字地址结构,包括地址族,ip和端口号 31 /*sockfd 绑定到套接字地址*/ 32 if(bind(sockfd , (struct sockaddr *)(&server) , sizeof(struct sockaddr))==-1) 33 { 34 printf("Bind error : %s\n",strerror(errno)); 35 exit(1); 36 } 37 /*sockfd 进入监听状态*/ 38 if(listen(sockfd , 1)==-1) 39 { 40 printf("Listen error : %s\n",strerror(errno)); 41 exit(1); 42 } 43 sin_size = sizeof(struct sockaddr_in); 44 /*接收连接请求*/ 45 if((new_fd = accept(sockfd , (struct sockaddr *)(&client) , &sin_size))==-1) 46 { 47 printf("Accept error"%s\n",strerror(errno)); 48 exit(1); 49 } 50 printf("You got a connection from client's ip is %s , port is %d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); 51 52 /*传输数据*/ 53 if(write(new_fd , "hello" , strlen("hello"))==-1) 54 { 55 printf("Write error : %s\n",strerror(errno)); 56 exit(1); 57 } 58 /*关闭套接字*/ 59 close(new_fd); 60 close(sockfd); 61 62 return 0; 63 }
客户端代码:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<errno.h> 4 #include<string.h> 5 #include<netinet/in.h> 6 #include<arpa/inet.h> 7 #include<unistd.h> 8 int main() 9 { 10 int sockfd; 11 char buffer[1024]; 12 struct sockaddr_in server; 13 int port = 5050 ,nbytes; //参见服务端代码 14 char *serverip = "127.0.0.1"; //设置服务器地址为本机 15 /*创建套接字*/ 16 if((sockfd = socket(AF_INET , SOCK_STREAM , 0))==-1) 17 { 18 printf("Socket error:%s\n",strerror(errno)); 19 exit(1); 20 } 21 /*连接服务器*/ 22 bzero(&server , sizeof(struct sockaddr_in)); 23 server.sin_family = AF_INET; 24 /*地址格式转换*/ 25 if(inet_aton(serverip , &server_addr.sin_addr)==0)//转换字节序同时给 sin_addr 赋值服务端 ip 26 { 27 printf("The server IP is not right !\n"); 28 exit(1); 29 } 30 server.sin_port = htons(port); 31 //填充服务端的套接字结构 32 33 /*调用 connect 函数来连接到服务器*/ 34 if((connect(sockfd , (struct sockaddr *)(&server) , sizeof(struct sockaddr)))==-1) 35 { 36 printf("Connect error:%s\n",strerror(errno)); 37 exit(1); 38 } 39 nbytes = read(sockfd , buffer , 1024); 40 if(nbytes < 0) 41 { 42 printf("Read error:%s\n",strerror(errno)); 43 exit(1); 44 } 45 close(sockfd); 46 printf("Received %d bytes :%s\n",nbytes , buffer); 47 return 0; 48 }
stdio.h : 包含标准输入输出函数 printf()
stdlib.h : 包含异常退出函数 exit()
errno.h : 包含报告错误信息函数 strerror(errno)
string.h : 包含字符串处理函数
netinet/in.h : 包含多个与网络程序相关的函数和数据结构
arpa/inet.h : 包含 write和close 函数
unistd.h : inet_ntoa()函数
有关 tcp/ip 协议和 大字节序小字节序,大家可以自行百度,我在这里就不详细叙述了。