要实现一个简易的服务器,需要几个步骤来与客户端建立连接,并接收客户端的数据进行处理。
上图是实现TCP客户/服务器程序需要使用到的基本套接字函数。
本篇以实现服务器端为主。
首先要调用socket函数得到一个监听套接字文件描述符作为起始,用于后续使用bind()和listen()
使用时需要包含头文件“#include
server.cpp
#include
int main(int argc, char* argv[]) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
...
}
原函数为 socket(int domain, int type, int protocal);
第一个参数domain表示你所使用的协议族,常用:
第二个参数type指定套接字类型,或者说是一种传输方式,常用:
第三个参数protocal设为某个协议的常值,或者设为0,通常设为0,它表示使用系统默认的前两个参数组合情况下使用的协议。这里AF_INET和SOCK_STREAM参数组合默认使用TCP协议。
通过socket()获得监听套接字文件描述符后,可以通过bind()来绑定协议地址给服务器。
server.cpp:
#include
#include
#define PORT 80
int main(int argc, char* argv[]) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(PORT);
bind(listen_fd, (struct sockaddr*)&address, sizeof(address));
...
}
原函数为:bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
第一个参数sockfd就是前面获取的套接字文件描述符,
第二个参数是一个指向地址结构的指针,需要将结构体内的几个参数进行初始化后使用,这里牵扯到了一个网络字节序的概念。
一般的操作系统都是用小端字节序(little endian),而网络中数据采用网络字节序也就是大端字节序(big endian),所以需要使用转化函数htonl()、htons()来实现主机字节序和网络字节序之间的转换。
htonl : host to network long
htons: host to network short
第三个参数是地址结构的长度。
绑定完后需要做的就是listen(),使用它来进行监听客户端的行为,并使得套接字的状态从CLOSED转换为LISTEN。
server.cpp:
#include
#include
#define PORT 80
int main(int argc, char* argv[]) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(PORT);
bind(listen_fd, (struct sockaddr*)&address, sizeof(address));
listen(listen_fd, 5);
...
}
原函数是:listen(int sockfd, int backlog);
第二参数backlog表示未完成连接队列 + 已完成连接队列的大小之和,一般设定为5,但是在Linux系统下实际值要乘一个称为模糊因子的数1.5,所以实际为8。
服务器调用accept()用于从已完成连接队列头部返回一个已完成连接文件名描述符(connect socket fd)
#include
#include
#define PORT 80
int main(int argc, char* argv[]) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(PORT);
bind(listen_fd, (struct sockaddr*)&address, sizeof(address));
listen(listen_fd, 5);
struct sockaddr_in client_addr;
socklen_t client_addrlength = sizeof(client_addr);
int connfd = accept(listenfd, (struct sockaddr*)&client_addr, &client_addrlength);
...
close(listen_fd);
close(connfd);
}
原函数为int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
第一个参数不用说了,第二个参数用于接收指向客户端的地址结构的指针,第三个参数是其大小。
因此需要创建一个客户端地址结构变量用于接收。