【版权声明:转载请保留出处:blog.csdn.net/gentleliu。邮箱:shallnew*163.com】
要说linux tcp服务器编程,首先需要讲一下网络编程中一些使用到的重要又基本的结构体和函数。\
结构体:
IPv4套接字地址结构struct sockaddr_in,定义在头文件<netinet/in.h>中,详细如下:
typedef uint32_t in_addr_t; typedef uint16_t in_port_t; typedef unsigned short sa_family_t; struct in_addr { in_addr_t s_addr; //32位IPv4地址,网络字节序 }; struct sockaddr_in { uint8_t sin_len; sa_family_t sin_family; //协议族类型 in_port_t sin_port; //端口号 struct in_addr sin_addr; //IP地址 char sin_zero[8]; //保留使用 };
通用套接字地址结构struct sockaddr,定义于<sys/socket.h>头文件中:struct sockaddr { uint8_t s_len; sa_family_t sa_family; char sa_data[14]; };
几个重要的函数:
(1)socket()函数:
#include<sys/socket.h> int socket(int family, int type, int protocol);
family参数指明了协议族,它的值通常为:AF_INET(IPv4协议)、AF_INET6(IPv6协议)、AF_ROUTE(路由套接口)、AF_LOCAL(UNIX域协议)、AF_KEY(密钥套接字)。type参数指明产生套接字的类型,它的值通常为:SOCK_STREAM(TCP使用这种形式)、SOCK_DGRAM(UDP使用这种形式)、SOCK_RAW(原始套接口)。protocol参数是协议标志,一般在调用socket()函数时将其置为0。(2)bind()函数:
#include<sys/socket.h> int bind(int sockfd, const struct sockaddr *seraddr, socklen_len addrlen);绑定函数的作用是为调用socket()函数产生的套接字分配一个本地协议地址,建立地址与套接字的对应关系。注意:协议地址addr是通用地址。sockfd参数是之前调用socket()函数返回的套接字描述符。server参数为一个结构体,其中包含了服务器端的地址信息。因此,必须在调用bind()函数之前,初始化该结构体,指定服务器端绑定的端口号以及IP地址。
(3)listen()函数:#include<sys/socket.h> int listen(int sockfd, int backlog);
该函数比较简单,调用listen()函数后,将未连接的套接字转换成被动套接字,使它处在监听模式下,服务器的状态从CLOSED转换到了LISTEN状态。(4)accept()函数:
#include<sys/socket.h> int accept(int listenfd, struct sockaddr *client, socklen_t *addr_len);
accept()函数使服务器接受客户端的连接请求。该函数最多返回三个值:一个既可能是新套接字也可能是错误指示的整数,一个客户进程的协议地址(由client所指),以及该地址的大小(这后两个参数是值-结果参数);也就是说,服务器可以通过参数client来得到请求连接并获得成功的客户的地址和端口号。在实际使用中,如果服务器端并不想保存客户的地址和端口号,也可以使用accept(listenfd, NULL, NULL)。至此,服务器与客户端的连接便建立了,接下来便是数据的发送和接收。发送和接收数据可以使用write(),read()/send(),recv()函数。
(5) connect函数:
#include <sys/socket.h> int connect( int sockfd, const struct sockaddr *seraddr, socklen_t addrlen);客户端使用该函数来与服务器建立连接,第二个参数套接字地址必须包含服务器的IP地址和端口号,客户端在调用该函数前不一定要调用bind函数,内核会确定源IP,并且选定一个临时端口作为源端口。
(6)send()函数:#include<sys/types.h> #include<sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数sockfd为套接字描述符,参数buf指向一个用于发送信息的数据缓冲区,len指明传送数据缓冲区的大小。参数flags是传输控制标志,当值为0时,函数所作的操作与write()相同。(7)recv()函数:
#include<sys/types.h> #include<sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags);
(8)close()函数:#include<unistd.h> int close(int sockfd); close()函数用于关闭套接字。sockfd是要关闭的描述符。
客户端和服务端编程流程:服务端:
socket()建立套接字
bind()套接字与地址(IP和端口)的绑定
listen()服务器监听客户端的连接
accept()接收客户端连接
send(), recv()接收和发送数据
close()关闭套接字
客户端:
socket()建立套接字
connect()连接服务器
send(),recv()读写网络数据
close()关闭套接字
下面时一个客户端程序和服务端程序实现:
服务器:
#include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> #include <arpa/inet.h> #include <netinet/in.h> #include "server.h" #include "ly_debug.h" void accept_handle(int connfd, struct sockaddr_in peeraddr); int main(int argc, const char *argv[]) { int sockfd, connfd; struct sockaddr_in seraddr, peeraddr; socklen_t addrlen; COMPILE_TIME_PRINT; if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); return -1; } sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { LY_ERR("socket: %s\n", strerror(errno)); return -1; } seraddr.sin_family = AF_INET; seraddr.sin_port = ntohs(atoi(argv[1])); seraddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sockfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr))) { LY_ERR("bind: %s\n", strerror(errno)); return -1; } if (listen(sockfd, MAX_CONN) < 0) { LY_ERR("listen: %s\n", strerror(errno)); return -1; } addrlen = sizeof(struct sockaddr); for (;;) { connfd = accept(sockfd, (struct sockaddr *)(&peeraddr), &addrlen); if (connfd < 0) { LY_ERR("accept: %s\n", strerror(errno)); continue; } accept_handle(connfd, peeraddr); } return 0; } void accept_handle(int connfd, struct sockaddr_in peeraddr) { char buf[32] = "Hello, welcome to server!"; LY_IFO("Receive request from %s, port %d\n", inet_ntop(AF_INET, &peeraddr.sin_addr, buf, sizeof(buf)), ntohs(peeraddr.sin_port)); //sleep(10); if (send(connfd, buf, sizeof(buf), 0) < 0) { LY_ERR("send: %s\n", strerror(errno)); return; } close(connfd); LY_IFO("send info: %s\n", buf); }
客户端:#include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include "ly_debug.h" int main(int argc, const char *argv[]) { int sockfd; struct sockaddr_in seraddr; char recvbuf[32]; COMPILE_TIME_PRINT; if (argc != 3) { LY_PRT("Usage: %s <ip> <port>\n", argv[0]); return -1; } if (inet_pton(AF_INET, argv[1], &seraddr.sin_addr) < 0) { LY_ERR("inet_pton: %s\n", strerror(errno)); return -1; } sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { LY_ERR("socket: %s\n", strerror(errno)); return -1; } seraddr.sin_family = AF_INET; seraddr.sin_port = htons(atoi(argv[2])); if (connect(sockfd, (struct sockaddr *)(&seraddr), sizeof(struct sockaddr)) < 0) { LY_ERR("connect: %s\n", strerror(errno)); return -1; } if (recv(sockfd, recvbuf, sizeof(recvbuf), 0) < 0) { LY_ERR("recv: %s\n", strerror(errno)); return -1; } LY_PRT("From server: %s\n", recvbuf); close(sockfd); return 0; }
服务器运行,接受到后面客户端的连接后执行如下:客户端做如下运行:
该服务器功能很简单,循环接收客户端连接,当客户端连接上之后给客户端回复一个欢迎信息。
在上面例子中,如果服务器在处理每一个客户请求时sleep 10秒,这时当有2个客户先后同时连接时,服务器处理完2个客户端要花几乎20秒时间,因为服务器要处理完一个客户连接才能接下去处理第二个服务器的连接,下一节的服务器程序可以只花10秒处理多个几乎同时先后连接到服务器的客户端请求。
该示例服务器为一个迭代服务器,当一个客户端的请求可能花费很长时间时,我们肯定不希望这个服务器被但个客户端长时间占用,而是希望服务器可以同时处理多个客户的请求,此时的服务器就是并发服务器,在下一节我们将开始编写并发服务器。