Linux下的Socket编程大体上包括Tcp Socket、Udp Socket即Raw Socket这三种,其中TCP和UDP方式的Socket编程用于编写应用层的socket程序,是我们用得比较多的,而Raw Socket则用得相对较少,不在本文介绍范围之列。
TCP Socket
基于TCP协议的客户端/服务器程序的一般流程一般如下:
它基本上可以分为三个部分:
一、建立连接:
- 服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态
- 客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答
- 服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。
二、传输数据:
建立连接后,TCP协议提供全双工的通信管道,服务器端和客户端根据协议可以通过read和write的反复调用实现数据的传输
三、关闭连接:
当数据传输已经完成后,服务器和客户端可以调用Close关闭连接,一端关闭连接后,另一端read函数则会返回0,可以根据这个特征来感应另一端的退出。
下面就以一个简单的EchoServer演示一下如何创建服务器端和客户端代码,其中和socket相关api都会高亮显示。
服务器端示例:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define MAXLINE 80 #define SERV_PORT 8000 int main(void) { char buf[MAXLINE]; int listenfd = 0; listenfd = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in servaddr = {0}; servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(listenfd, (sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20); printf("Accepting connections ...\n"); while (1) { sockaddr_in cliaddr = {0}; socklen_t cliaddr_len = sizeof(cliaddr); int connfd = accept(listenfd, (sockaddr *)&cliaddr, &cliaddr_len); char str[INET_ADDRSTRLEN]; printf("connected from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); while(true) { int count = read(connfd, buf, MAXLINE); if (count == 0) break; write(connfd, buf, count); } close(connfd); printf("closed from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); } }
PS:这里需要注意的一下的是sock函数的第二个参数SOCK_STREAM,它表示是一个TCP连接,后面我们会介绍通过传入SOCK_DGRAM打开udp连接。
服务器端主体流程就是一个死循环,它接受一个socket连接,然后将其原封不动的返回给客户端,待客户端退出后,关闭socket连接,再次接受下一个socket连接。
客户端代码如下:
#include <stdio.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define MAXLINE 80 #define SERV_PORT 8000 #define MESSAGE "hello world" int main(int argc, char *argv[]) { char buf[MAXLINE]; int sockfd = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in servaddr = {0}; servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); if (0 != connect(sockfd, (sockaddr *)&servaddr, sizeof(servaddr))) { printf("connected failed"); return 1; } write(sockfd, MESSAGE, sizeof(MESSAGE)); int count = read(sockfd, buf, MAXLINE); printf("Response from server: %s\n",buf); close(sockfd); return 0; }
客户端代码比较简单,这里就不多介绍了。
UDP Socket
典型的UDP客户端/服务器通讯过程如下图所示:
由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实现,可能反而会需要更多代码。
典型的示例如下:
/* server.cpp */ #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #define MAXLINE 80 #define SERV_PORT 8000 int main(void) { char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int sockfd = socket(AF_INET, SOCK_DGRAM, 0); sockaddr_in servaddr = {0}; servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(sockfd, (sockaddr *)&servaddr, sizeof(servaddr)); printf("Accepting connections ...\n"); while (1) { sockaddr_in cliaddr; socklen_t cliaddr_len = sizeof(cliaddr); int count = recvfrom(sockfd, buf, MAXLINE, 0, (sockaddr *)&cliaddr, &cliaddr_len); if (count < 0) { printf("recvfrom error"); continue; } printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); sendto(sockfd, buf, count, 0, (sockaddr *)&cliaddr, sizeof(cliaddr)); } }
/* client.cpp */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #define MAXLINE 80 #define SERV_PORT 8000 int main(int argc, char *argv[]) { char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int sockfd = socket(AF_INET, SOCK_DGRAM, 0); sockaddr_in servaddr = {0}; servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); while (fgets(buf, MAXLINE, stdin) != NULL) { int count = sendto(sockfd, buf, strlen(buf), 0, (sockaddr *)&servaddr, sizeof(servaddr)); if (count == -1) { printf("sendto error"); return 0; } count = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0); if (count == -1) { printf("recvfrom error"); return 0; } write(STDOUT_FILENO, buf, count); } close(sockfd); return 0; }