天天看点

回射客户-服务器模型(1)

最近在学习socket编程,根据自己的学习过程及学习笔记,下面来梳理一下如何实现一个简单的回射客户-服务器模型,也借此来熟悉一下socket、bind、listen、accept、connect这些函数的使用。

简单的回射客户/服务器模型

回射客户-服务器模型(1)

下面先看一下一个客户/服务器模型的框架图。

回射客户-服务器模型(1)

可以看到,服务器创建过程一般是:

1)创建套接字,使用socket函数,这个时候的套接字是主动套接字;

2)初始化服务器端地址,并使用bind函数将套接口与该地址进行绑定;

3)监听,使用listen函数,将套接口从close状态转为监听状态才能够接收客户端发起的连接,同时经过监听之后,服务器端的套接字从主动状态(发起连接)变为被动状态(接收连接);

4)等待接收连接,accept函数,使用该函数后,服务器端一直处于阻塞状态,直到客户端的连接到达;

5)等客户端的连接到达后,双方开始进行通信(读写数据);

客户端创建的过程就相对简单了:

1)创建套接字;

2)使用connect函数发起连接(过程中要经过三次握手);

3)连接成功,双方相互进行通信;

下面以代码的方式具体说明每一步骤的实现。

服务器端:

// 1. 创建套接字
int listenfd;
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	perror("socket create error");

// 2. 初始化地址
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;//地址族
servaddr.sin_port = htons(5188); //端口
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);// 表示本机的任意地址,当然也可以自己指定
//servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

// 3. 绑定bind
if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
	perror("bind error");

// 4. 监听listen
if(listen(listenfd, SOMAXCONN) < 0)//第二个参数表示每一个端口的最大监听队列长度
	perror("listen error");
	
// 5. 接收连接
// 在接收连接前,先定义一个地址结构,用来保存对等方的地址
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
memset(&peeraddr, 0, peerlen);
int conn; 
// 成功返回对等方的套接口
if((conn = accept(lisenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
	perror("accept error");

//输出客户端的地址和端口(仅测试用)
printf("IP=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));

// 至此,双方连接已完成,可以进行通信
char recvbuf[1024];
while(1)
{
	memset(recvbuf, 0, sizeof(recvbuf));
	int ret = read(conn, recvbuf, sizeof(recvbuf));
	fputs(recvbuf, stdout);
	//再回射给客户端
	write(conn, recvbuf, ret);
}

//关闭套接口
close(lisenfd);
close(conn);
           

需要对上述的一些步骤做一些说明:

1)在初始化地址的时候,服务器端地址可以指定为具体某一IP,也可以是INADDR_ANY。这里建议选用INADDR_ANY参数,表示地址0.0.0.0,也就是不确定的地址,或“所有地址”、“任意地址”。如果你的服务器有多个网卡,你要在5188这个端口上监听,所有发送到服务器的这个端口,不管是哪个网卡/哪个IP地址接收到的数据,都可以进行处理。

2)关于listen函数中的第二个参数。每一个处于监听状态的端口,都要自己的监听队列,那么该参数就指定了监听队列的长度。我们可以自己指定一个正整数,也可以使用参数SOMANCONN,它表示每个端口的最大监听队列长度。

客户端:

// 1. 创建套接字
int sock;
if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	perror("socket create error");
// 2. 初始化一个你要连接的对方的地址
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

// 3. 发起连接
if(connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
	perror("connect error");

// 4. 进行通信(读写数据)
char recvbuf[1024] = {0};
char sendbuf[1024] = {0};

while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
	//发送数据(请求连接)
	write(sock, sendbuf, strlen(sendbuf));
	//读取服务器端回射来的数据
	read(sock, recvbuf, sizeof(recvbuf));
	fputs(recvbuf, stdout);
	//每次读取完毕需要清空缓冲区
	memset(sendbuf, 0, sizeof(sendbuf));
	memset(recvbuf, 0, sizeof(recvbuf));
}
//关闭套接口
close(sock);