当前位置: 首页 > news >正文

【网络编程】简单的网络服务器设计

九、简单网络服务器设计

不同于客户端程序,服务器端程序需要同时为多个客户端提供服务,及时响应。比如Web 服务器,就要能同时处理不同IP 地址的主机发来的浏览请求,并把网页及时反应给浏览器。 因此,开发服务器程序,必须要能实现并发服务能力。这是网络服务器之所以成为服务器的最 本质的特点。

这里要注意,有些并发并不是非常需要精确同时。在某些应用场合,比如每次处理客户端 数据量较少的情况下,我们也可以简化服务器的设计。通常来讲,网络服务器的设计模型有循 环服务器、I/O 复用服务器、多线程并发服务器。

9.1 循环服务器

循环服务器在同一时刻只可以响应一个客户端的请求。循环服务器指的是对于客户端的请 求和连接,服务器在处理完毕一个之后再处理另一个,即串行处理客户的请求。这种类型的服 务器一般适用于服务器与客户端一次传输的数据量较小、每次交互的时间较短的场合。根据使 用的网络协议不同(UDP 或 TCP), 循环服务器又可分为无连接的循环服务器和面向连接的 循环服务器。其中,无连接的循环服务器也称UDP 循环服务器, 一般用在网络情况较好的场 合,比如局域网中。面向连接使用了TCP 协议,可靠性大大增强,所以可用在因特网上,但 开销相对于无连接的服务器而言也较大。

9.1.1 UDP循环服务器

UDP 循环服务器的实现方法是:UDP 服务器每次从套接字上读取一个客户端的请求,然 后处理,再将处理结果返回给客户机。算法流程如下:

socket(...);
 bind(...);
while(1) {
	recvfrom(...); 
	process(...);
   	sendto(...);
}

因为UDP 是非面向连接的,所以没有一个客户端可以老是占用服务器端的,服务器对于 每一个客户机的请求总是能够满足。

9.1.2 TCP循环服务器

TCP服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后断开连接。 面向连接的循环服务器工作流程如下:
第一步,创建套接字并将其绑定到指定端口,然后开始监听。
第二步,当客户端连接到来时,accept 函数返回新的连接套接字。 第三步,服务器在该套接字上进行数据的接收和发送。
第四步,在完成与该客户端的交互后关闭连接,返回执行第二步。 写成代码就是:

socket(...); 
bind(...);
listen(...);
 while(1)   {
	accept(...);
	process(...);
	close(...);
 }

TCP循环服务器一次只能处理一个客户端的请求。只有在这个客户的所有请求都满足后, 服务器才可以继续后面的请求。这样如果有一个客户端占住服务器不放时,其他客户机就都不 能工作了,因此TCP 服务器一般很少用循环服务器模型。

【例9.1】一个简单的TCP 循环服务器

服务端

#include <stdio.h>
#include <tchar.h>
#include <winsock.h>

#pragma comment(lib,"wsock32")

#define  BUF_SIZE  200
#define PORT 2048

int _tmain(int argc, _TCHAR* argv[])
{
	struct   sockaddr_in fsin;
	SOCKET    clisock;
	WSADATA  wsadata;
	int      alen, connum = 0;
	char     buf[BUF_SIZE] = "hi,client";

	struct  servent* pse;    /* server information    */
	struct  protoent* ppe;    /* proto information     */
	struct sockaddr_in sin;   /* endpoint IP address   */
	int  s;

	if (WSAStartup(MAKEWORD(2, 0), &wsadata) != 0)
	{
		puts("WSAStartup failed\n");
		WSACleanup();
		return -1;
	}
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_port = htons(PORT);

	/* get protocol number from protocol name */
	if ((ppe = getprotobyname("TCP")) == 0)
	{
		printf("  get protocol number error \n");
		WSACleanup();
		return -1;
	}


	s = socket(PF_INET, SOCK_STREAM, ppe->p_proto);
	if (s == INVALID_SOCKET)
	{
		printf(" creat socket error \n");
		WSACleanup();
		return -1;
	}

	if (bind(s, (struct sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
	{
		printf("  socket bind error \n");
		WSACleanup();
		return -1;
	}


	if (listen(s, 10) == SOCKET_ERROR)
	{
		printf("  socket listen error \n");
		WSACleanup();
		return -1;
	}

	while (1)
	{
		alen = sizeof(struct sockaddr);
		clisock = accept(s, (struct sockaddr*)&fsin, &alen);

		if (clisock == INVALID_SOCKET)
		{
			printf("initalize failed\n");
			WSACleanup();
			return -1;
		}
		connum++;
		send(clisock, buf, strlen(buf), 0);
		printf("%d  client  comes\n", connum);
		closesocket(clisock);
	}

	return 0;
}

在上述代码中,每接受一个客户端连接,就发送一段数据,然后关闭客户端连接,再次监 听下一个连接请求。

客户端

#include <stdio.h>
#include <tchar.h>

#include <winsock.h>
#pragma comment(lib,"wsock32")

#define  BUF_SIZE  200
#define PORT 2048

int _tmain(int argc, _TCHAR* argv[])
{
	char  host[] = "localhost";

	char   buff[BUF_SIZE];
	SOCKET  s;
	int    len;
	WSADATA  wsadata;

	struct hostent* phe;      /*host information     */
	struct servent* pse;      /* server information  */
	struct protoent* ppe;     /*protocol information */
	struct sockaddr_in sin;   /*endpoint IP address  */
	int   type;


	if (WSAStartup(MAKEWORD(2, 0), &wsadata) != 0)
	{
		printf("WSAStartup failed\n");
		WSACleanup();
		return -1;
	}

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
	//get IP address from  host name 
	if (phe = gethostbyname(host))
		memcpy(&sin.sin_addr, phe->h_addr, phe->h_length); /*  host IP address  */
	else if ((sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE)
	{
		printf("get host IP information error \n");
		WSACleanup();
		return -1;
	}

	/**** get protocol number  from protocol name  ****/
	if ((ppe = getprotobyname("TCP")) == 0)
	{
		printf("get protocol information error \n");
		WSACleanup();
		return -1;
	}
	/**** creat a socket description ****/
	s = socket(PF_INET, SOCK_STREAM, ppe->p_proto);

	if (s == INVALID_SOCKET)
	{
		printf(" creat socket error \n");
		WSACleanup();
		return -1;
	}
	if (connect(s, (struct sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
	{
		printf("connect socket  error \n");
		WSACleanup();
		return -1;
	}
	while (0 == (len = recv(s, buff, sizeof(buff), 0)))
		;
	buff[len - 1] = '\0';
	printf("%s\n", buff);
	closesocket(s);
	WSACleanup();
	return 0;
}

9.2 多线程并发服务器

并发服务器在同一个时刻可以响应多个客户端的请求。多线程并发TCP 服务器可以同时 处理多个客户请求,并发服务器常见的设计是“一个请求一个线程”:针对每个客户请求,主 线程都会单独创建一个工作者线程,由工作者线程负责和客户端进行通信。多线程并发服务器 的工作流程如下:

  • (1)主线程创建套接字并将其绑定到指定端口,然后开始监听。
  • (2)重复调用 accept 函数,当客户端连接到来时创建一个工作者线程处理请求。
  • (3)工作者线程接受客户端请求,与客户端进行交互(发送或接收消息)。
  • (4)工作者线程在交互完毕后关闭连接并退出。 代码算法如下:
socket(...);
bind(...);
listen(...);
while(1){
	accept(...);
	if(fork(..)==0){
		CreateThread(...);                    // 创建线程来处理
		close(...); 
	}
	exit(...);
	close(...); 
}

TCP 并发服务器可以解决TCP 循环服务器客户机独占服务器的情况,但同时也带来了问 题:为了响应客户的请求,服务器要创建线程来处理,而创建线程是一种非常消耗资源的操作, 这也就要求服务器的硬件配置要好。

9.3 I/O 复用服务器

应用程序发起I/O 操作,系统内核缓冲I/O 数据,当某个I/O 准备好后,系统通知应用程 序 该IO 可读或可写,这样应用程序可以马上完成相应的IO 操作,而不需要等待系统完成相 应 的IO 操作,从而使应用程序不必因等待IO 操作而阻塞。

I/O复用的典型模型之一是Select 模 型,它的工作流程如下:

  • (1)清空描述符集合。
  • (2)建立需要监视的描述符与描述符集合的关系。
  • (3)调用 select 函数。
  • (4)检查监视的描述符判断是否已经准备好
  • (5)对已经准备好的描述符进行IO 操作。

如果你希望服务器仅仅检查是否有客户在等待连接,有就接受连接,否则继续做其他事情, 那么可以通过使用select 调用来实现。除此之外,select 还可以同时监视多个套接字。

相关文章:

  • 编程题-计算器(中等)
  • 耘锄、铧式犁、畦作
  • 实现静态网络爬虫(入门篇)
  • openwrt路由系统------Linux 驱动开发的核心步骤
  • 传输层协议
  • 信息系统运行管理员教程9--大型网站运维
  • 两种免费防御DDoS攻击的实战攻略,详细教程演示
  • 力扣-股票买入问题
  • 骑砍Ⅱ霸主MOD开发(27)-定制化GameEntity-MissionWeapon
  • 【Manus】AI 代理人正式上岗-附Manus邀请码限时通道
  • 2.2 Windows本地部署DeepSeek模型 --- Ollama篇(下)
  • Training-free Neural Architecture Search for RNNs and Transformers(预览版本)
  • 基于Rye的Django项目通过Pyinstaller用Github工作流简单打包
  • [原创](Modern C++)现代C++的关键性概念: 非常独特的std::sentinel_for概念(哨兵概念)
  • LeetCode Hot100刷题——反转链表(迭代+递归)
  • 【c++】反转字符串
  • 二次SQL注入
  • 线程安全问题(面试重难点)
  • Python asyncIO 面试题及参考答案 草
  • 【数据结构与算法】Java描述:第二节:LinkedList 链表
  • 什么网站可以做装修效果图/巨量算数
  • flash网站制作教程 下载/seo自动优化工具
  • 北京景网站建设/竞价推广的企业
  • 公司网站如何做公安部备案/代写
  • 做网站哪个公司最/友博国际个人中心登录
  • 网站国际化怎么做/舆情网站入口