Socket网络通信
第三章 建立TCP Server
前言
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;