1.前言
要实现一个简易的服务器,需要几个步骤来与客户端建立连接,并接收客户端的数据进行处理。
 
 上图是实现TCP客户/服务器程序需要使用到的基本套接字函数。
 本篇以实现服务器端为主。
2.sokcet()
首先要调用socket函数得到一个监听套接字文件描述符作为起始,用于后续使用bind()和listen()
 使用时需要包含头文件“#include <sys/socket.h>”。
 server.cpp
#include <sys/socket.h>
int main(int argc, char* argv[]) {
	int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	... 
}
原函数为 socket(int domain, int type, int protocal);
 第一个参数domain表示你所使用的协议族,常用:
- AF_INET : IPv4协议族
- AF_INET6 : IPv6协议族
- AF_LOCAL, AF_UNIX: 本地交互
第二个参数type指定套接字类型,或者说是一种传输方式,常用:
- SOCK_STREAM: 字节流套接字
- SOCK_DGRAM:数据包套接字
第三个参数protocal设为某个协议的常值,或者设为0,通常设为0,它表示使用系统默认的前两个参数组合情况下使用的协议。这里AF_INET和SOCK_STREAM参数组合默认使用TCP协议。
3.bind()
通过socket()获得监听套接字文件描述符后,可以通过bind()来绑定协议地址给服务器。
 server.cpp:
#include <sys/socket.h>
#include <arpa/inet.h>
#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
 第三个参数是地址结构的长度。
4.listen()
绑定完后需要做的就是listen(),使用它来进行监听客户端的行为,并使得套接字的状态从CLOSED转换为LISTEN。
 server.cpp:
#include <sys/socket.h>
#include <arpa/inet.h>
#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。
 
5.accept()
服务器调用accept()用于从已完成连接队列头部返回一个已完成连接文件名描述符(connect socket fd)
#include <sys/socket.h>
#include <arpa/inet.h>
#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);
 第一个参数不用说了,第二个参数用于接收指向客户端的地址结构的指针,第三个参数是其大小。
 因此需要创建一个客户端地址结构变量用于接收。










