【Socket网络通信 | 3.建立TCP Server】

Socket网络通信



前言

1、htons(port) 转换字节序

host to network: 本地字节序转换成网络字节序
Big Endian:大端字节序(第一位字节序在后,内存地址较小的在后,高位在前)
Little Endian:小端字节序(第一位字节序在前,内存地址较小的在前,高位在后)
举个栗子,一个98的数据写入内存中,9存于数据高位、8存于数据低位,对于小端字节序,8存在前面,9存在后面,对于大端字节序的系统,则会相反
在这里插入图片描述
字节序在不同系统上会有所差异,大端和小端数据存储是相反的,如果在网络传输,他们字节序是要求一致的;因此在传输前访问这些数据,必须要有一个统一标准。

在这里插入图片描述
比如这个例子,大端字节序存储结构系统,传输数据给小端字节序存储结构系统,大端字节序由于高字节在前,前16bit数据2,以2进制形式为0b0000 0000 0000 0010 ,小端字节序由于低字节序在前地捕获数据,会认为后8bit 0b0000 0010 是数据的高位,最终会变成 0b0000 0010 0000 0000,理解成其他数据了;

因此这个端口号需要进行大小端字节序的转换,才能被正确统一

另外要理解好这个“字节序”的概念,不是bit序,是一个一个字节的次序,如果是单个字节,则无需考虑字节序问题

2、sock_addr.sin_addr 指定IP地址

sockaddr_in sock_addr;
sock_addr.sin_addr = 

2.1、绑定本地IP地址(127.0.0.1)——只能本地访问这个网络
2.2、绑定外网IP地址——只能外部访问,内部不能访问
2.3、一台机器多个网卡,需要指定某个网卡的IP
2.4、htonl(0)——支持任意IP访问

s_addr 是用一个int类型(4字节)来表示IP,一个字节相当于unsigned char,值0~255,假设IPv4地址192.168.1.100,正好4个字节

一、Server 端必用的几个API

1.bind (用于绑定端口)

发给某个网卡的某个IP地址的某个端口,这个端口实际上是一个用户编辑的应用程序,相当于外界的网络数据发送到Sever端的具体端口号,由用户应用程序处理改指定端口号收到的通信内容

#ifdef WIN32 
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>  //close 函数对应的头文件 
#include <arpa/inet.h>
#define  closesocket  close //Linux 系统下没有 closesocket 指令,只有 close 指令 
#endif
#include <stdio.h>
#include <stdlib.h>


int main(int argc,char* argv[])
{
	unsigned short port =  8080;//默认端口号等于8080 
	int i;
#ifdef WIN32 
	//1、初始化动态链接库 
	WSADATA ws;//定义一个结构体对象 
	/*
	MAKEWORD定义版本号,把结构体传过去,把对动态库的引用+1 ;
	Windows下对动态库的引用不会反复添加,当所有程序都退出这个库的时候,才会从内存卸载 
	*/
	WSAStartup(MAKEWORD(2,2),&ws);//不调用的话,会导致Socket失败 
#endif
	//2、创建 Socket 套接字 
	//AF_INET是属于TCP/IP的网络通信协议,用户还可以使用其他的一些诸如蓝牙之类的协议 ;传输层是TCP Or UDP ;最后一个参数在做原始套接字的时候可能会用到 
	int sock = socket(AF_INET,SOCK_STREAM,0); 
	
	//对于Socket通信的步骤,每一步都必须判断是否成功,成功的判断标准就是返回值是否<0
	if(sock == -1)
	{
		//Windows一般资源耗尽才会创建失败,一般很难失败;但Linux上的每个任务分配的(句柄数量)都是有限的 ~ 默认1024个 
		printf("Create Socket Failed !\r\n");
		return -1;		
	} 
	//3、Server端需要先绑定一个端口(网络中发给相应的网卡-->IP地址-->端口,而这个对应的端口有它自己的启用程序负责响应) 
	struct sockaddr_in sock_addr;
	//3.1、响应TCP协议数据 
	sock_addr.sin_family = AF_INET;
	if(argc > 1)
	{
		port = atoi(argv[1]);
	} 
	//3.2、大小端字节序的转换 
	sock_addr.sin_port = htons(port);//host to network本地字节序转换成网络字节序 (目前网络字节序都是大端字节序,WINDOWS X86体系用的都是小端字节序) 
	//3.3、任意IP地址 
	sock_addr.sin_addr.s_addr = htonl(0);
	//3.4、把端口号、IP地址等信息绑定到当前这个端口号中 
	if(bind(sock,(struct sockaddr*)&sock_addr,sizeof(sock_addr))!=0)//第三个参数传输大小是因为这是一个C语言的函数,为了能实现重载,根据结构体大小不同来调用不同的处理方式 
	{
		printf("bind port %d failed !\r\n",port);//绑定端口很容易失败,这部分信息还是要打印或者调试 
		return -2; 
	}
	printf("bind port %d success !\r\n",port);//端口这个资源当你绑定,没有把它释放 ;或者你释放掉,但这个资源没有马上被释放 ,会造成你第二次绑定还不能成功 
	//测试环境的时候可以频繁去绑定同一个端口,但生产环境的时候不会 
	 
	//、打印后关闭掉 Socket 
	closesocket(sock); 
	//Linux Console: ulimit -n #查询单进程最大限制资源数  ulimit -n 3000 可以把最大限制资源数设置到 3000 

	return 0;
}

2.listen (监听端口)

bind 只绑定相应的端口号,但接收该端口号的信息仍需要在之前调用 listen 监听该端口

/************************************************************
Function Description : 面向连接的服务器,使用它将一个套接字置
					为被动模式,并准备接收传入连接。用于服务器
					,指明某个套接字连接是被动的
Function name : int listen(int sockfd,int input_queue_size);
Function Parameter :
	@Para1 : sockfd-套接字描述符,指明创建连接的套接字
	@Para2 : input_queue_size-该套接字使用的队列长度,指定在
			请求队列中允许的最大请求数(并不是并发量)
eg : listen(sockfd,20)
************************************************************/

每一个连接到服务器上,都会存一个信息,信息包括对方的 IP 、端口等;这些信息都会存到一个列表当中,这些列表能存的信息量就是有 input_queue_size 决定;
如果已经服务器接收了10个连接了,但仍然没通过 accept 进行接收,再有新的连接进来的时候,就会将其丢掉

更直白的理解应该是一个缓存大小,在一定时间内最大能承载的连接量 ;Socket 实际上是一个文件,每接收一个内容都会往文件里面写东西,如果没有用 accept 进行读取的时候,超过了文件大小的限制,则没法再往里面存内容了。而 accept 读取一个内容,则缓存会 -1

#ifdef WIN32 
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>  //close 函数对应的头文件 
#include <arpa/inet.h>
#define  closesocket  close //Linux 系统下没有 closesocket 指令,只有 close 指令 
#endif
#include <stdio.h>
#include <stdlib.h>


int main(int argc,char* argv[])
{
	unsigned short port =  8080;//默认端口号等于8080 
	int i;
#ifdef WIN32 
	//1、初始化动态链接库 
	WSADATA ws;//定义一个结构体对象 
	/*
	MAKEWORD定义版本号,把结构体传过去,把对动态库的引用+1 ;
	Windows下对动态库的引用不会反复添加,当所有程序都退出这个库的时候,才会从内存卸载 
	*/
	WSAStartup(MAKEWORD(2,2),&ws);//不调用的话,会导致Socket失败 
#endif
	//2、创建 Socket 套接字 
	//AF_INET是属于TCP/IP的网络通信协议,用户还可以使用其他的一些诸如蓝牙之类的协议 ;传输层是TCP Or UDP ;最后一个参数在做原始套接字的时候可能会用到 
	int sock = socket(AF_INET,SOCK_STREAM,0); 
	
	//对于Socket通信的步骤,每一步都必须判断是否成功,成功的判断标准就是返回值是否<0
	if(sock == -1)
	{
		//Windows一般资源耗尽才会创建失败,一般很难失败;但Linux上的每个任务分配的(句柄数量)都是有限的 ~ 默认1024个 
		printf("Create Socket Failed !\r\n");
		return -1;		
	} 
	//3、Server端需要先绑定一个端口(网络中发给相应的网卡-->IP地址-->端口,而这个对应的端口有它自己的启用程序负责响应) 
	struct sockaddr_in sock_addr;
	//3.1、响应TCP协议数据 
	sock_addr.sin_family = AF_INET;
	if(argc > 1)
	{
		port = atoi(argv[1]);
	} 
	//3.2、大小端字节序的转换 
	sock_addr.sin_port = htons(port);//host to network本地字节序转换成网络字节序 (目前网络字节序都是大端字节序,WINDOWS X86体系用的都是小端字节序) 
	//3.3、任意IP地址 
	sock_addr.sin_addr.s_addr = htonl(0);
	//3.4、把端口号、IP地址等信息绑定到当前这个端口号中 
	if(bind(sock,(struct sockaddr*)&sock_addr,sizeof(sock_addr))!=0)//第三个参数传输大小是因为这是一个C语言的函数,为了能实现重载,根据结构体大小不同来调用不同的处理方式 
	{
		printf("bind port %d failed !\r\n",port);//绑定端口很容易失败,这部分信息还是要打印或者调试 
		return -2; 
	}
	printf("bind port %d success !\r\n",port);//端口这个资源当你绑定,没有把它释放 ;或者你释放掉,但这个资源没有马上被释放 ,会造成你第二次绑定还不能成功 
	//测试环境的时候可以频繁去绑定同一个端口,但生产环境的时候不会 
	 
	 
	//4、listen 监听端口,并且规定在没有 accept 的情况下,当前这个 sock 只能缓存 10 个客户端连接 
	listen(sock,10);
	//5、打印后关闭掉 Socket 
	getchar();//这个获取输入可以让程序阻塞等待 
 	closesocket(sock); 
	//Linux Console: ulimit -n #查询单进程最大限制资源数  ulimit -n 3000 可以把最大限制资源数设置到 3000 
	
	return 0;
}

3.accept (获取用户的连接信息)

/*****************************************************************************************
Function Description : 主要用在基于连接的套接字类型,比如
						SOCK_STREAM和SOCK_SEQPACKET。它提取出
						所监听套接字的等待连接队列中第一个连接
						请求,创建一个新的套接字,并返回指向该
						套接字的文件描述符。
						新建立的套接字不在监听状态,原来所监听
						的套接字也不受该系统调用的影响
Function name : int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);;
Function Parameter :
	@Para1 : sockfd-套接字描述符,指明获取连接的套接字
	@Para2 : addr-客户端地址
	@Para3 : addrlen-客户端地址
eg : int client = accept(sock,0,0);//不需要知道它的返回信息(包括IP地址那些)
*****************************************************************************************/

实际上,accept 函数中的传入形参1 – sock 接口,是只用于存储之前创建 Socket、绑定端口、监听的 Socket 文件,而调用 accept 函数所返回的 client 文件描述符,是真正用于做信息传输,和客户端进行网络通信的。

每一个客户端的连接,都会生成一个 client,而形参1的 sock是被用于各个客户端建立连接的 Socket

在这里插入图片描述
获取客户端的连接,创建完 Socket 后,如果不对它进行操作,则需要对它进行关闭;(因为一般每建立了连接,会把新创建的这个 Socket 扔到一个新的线程当中;简单的方案是每获取一个客户端的连接就创建一个线程,复杂方案可以创建一个线程池,每来一个客户端就分配到一个闲置的线程进行处理)

	//5、accept 获取用户的连接信息
	int client = accept(sock,0,0);
	printf("accept client %d \r\n",client);
	
	closesocket(client);
	getchar();//这个获取输入可以让程序阻塞等待 
	//6、打印后关闭掉 Socket 
	closesocket(sock); 
	//Linux Console: ulimit -n #查询单进程最大限制资源数  ulimit -n 3000 可以把最大限制资源数设置到 3000 
	
	return 0;
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值