将底层复杂的协议体系,执行流程,进行了封装,封装完的结果,就是一个SOCKET,也就是说,SOCKET是咱们调用协议进行通讯的操做接口数组
数据类型:SOCKET 转定义:unsigned int服务器
在系统里每个socket对应着==惟一的一个整数==,好比23,对应着socket的协议等信息,在通讯中,就使用这些整数进行通讯,系统会自动去找这些整数所对应的协议
每一个客户端有一个socket,服务器有一个socket,通讯时就是经过socket,来表示和谁传递信息网络
/* 函数原型 */ SOCKET socket( int af, /*地址的类型*/ int type, /*套接字类型*/ int protocol /*协议类型*/ );
地址类型 | 形式 |
---|---|
==AF_INET== | 192.168.1.103(IPV4,4字节,32位地址) |
AF_INET6 | 2001:0:3238:DFE1:63::FEFB(IPV6,16字节,128位地址) |
AF_BTH | 6B:2D:BC:A9:8C:12(蓝牙) |
AF_IRDA | 红外 |
通讯地址不止只有IP地址
类型 | 用处 |
---|---|
==SOCK_STREAM== | 提供带有OOB数据传输机制的顺序,可靠,双向,基于链接的字节流。 使用传输控制协议(TCP)做为Internet地址系列(AF_INET或AF_INET6) |
SOCK_DGRAM | 支持数据报的套接字类型,它是固定(一般很小)最大长度的无链接,不可靠的缓冲区。使用用户数据报协议(UDP)做为Internet地址系列(AF_INET或AF_INET6) |
SOCK_RAW | 提供容许应用程序操做下一个上层协议头的原始套接字。 要操做IPv4标头,必须在套接字上设置IP_HDRINCL套接字选项。 要操做IPv6标头,必须在套接字上设置IPV6_HDRINCL套接字选项 |
SOCK_RDW | 提供可靠的消息数据报。 这种类型的一个示例是Windows中的实用通用多播(PGM)多播协议实现,一般称为可靠多播节目 |
SOCK_SEQPACKET | 提供基于数据报的伪流数据包 |
协议类型 | 用处 |
---|---|
IPPROTO_TCP | 传输控制协议(TCP) |
IPPROTO_UDP | 用户数据报协议(UDP) |
IPPROTO_ICMP | Internet控制消息协议(ICMP) |
IPPROTO_IGMP | Internet组管理协议(IGMP) |
IPPROTO_RM | 用于可靠多播的PGM协议 |
==填写0==表明系统自动帮咱们选择协议类型socket
== 参数一、二、3是要相互配套使用的==,不能随便填,使用不一样的协议就要添加不一样的参数
if (INVALID_SOCKET == socketServer) { int a = WSAGetLastError( ); WSACleanup(); return 0; }
SOCKET socketListen = socket(AF_INET,SOCK_STREAM,0); if(INVALID_SOCKET == socketListen) { int a = WSAGetLastError( ); WSACleanup(); return 0; }
做用:给socket绑定端口号与地址tcp
端口号函数
int bind ( SOCKET s, /*服务器建立的socket*/ const sockaddr *addr, /*绑定的端口和具体地址*/ int namelen /*sizeof(sockaddr)*/ );
定义一个==SOCKADDR_IN==数据类型,是一个结构体:测试
typedef struct sockaddr_in { #if(_WIN32_WINNT < 0x0600) short sin_family; /* 地址类型 */ #else //(_WIN32_WINNT < 0x0600) ADDRESS_FAMILY sin_family; #endif //(_WIN32_WINNT < 0x0600) USHORT sin_port; /* 端口号 */ IN_ADDR sin_addr; /* IP地址 */ CHAR sin_zero[8]; } SOCKADDR_IN, *PSOCKADDR_IN;
其中IN_ADDR sin_addr; 又是一个结构体
typedef struct in_addr { union { struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b; struct { USHORT s_w1,s_w2; } S_un_w; ULONG S_addr; } S_un; #define s_addr S_un.S_addr /* can be used for most tcp & ip code */ #define s_host S_un.S_un_b.s_b2 // host on imp #define s_net S_un.S_un_b.s_b1 // network #define s_imp S_un.S_un_w.s_w2 // imp #define s_impno S_un.S_un_b.s_b4 // imp # #define s_lh S_un.S_un_b.s_b3 // logical host } IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;
127.0.0.1 本地回环地址,用于本地网络测试,数据不出计算机端口号:0~65535spa
0~1013:系统保留占用端口号调试
si.sin_port = ==htons(12345)==;
si.sin_addr.S_un.S_addr = ==inet_addr("127.0.0.1")==;
si.sin_family = ==AF_INET==;
sizeof(sockadd)code
if (SOCKET_ERROR == bind(socketListen,(struct sockaddr*)&sockAddress,sizeof(sockAddress))) { printf("bind fail!"); //int nError = ::WSAGetLastError(); //关闭库 closesocket(socketListen); WSACleanup(); return -1; }
struct SOCKADDR_IN si; si.sin_family = AF_INET; /* 地址协议 */ si.sin_port = htons(12345); /* 端口号 */ si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); /*IP地址,点分十进制*/ if (SOCKET_ERROR == bind(socketListen,(struct sockaddr*)&sockAddress,sizeof(sockAddress))) { printf("bind fail!"); //int nError = ::WSAGetLastError(); //关闭库 closesocket(socketListen); WSACleanup(); return -1; }
做用:将socket置于侦听传入链接的状态(服务器能够接受客户端连接了)
int WSAAPI listen ( SOCKET s, /*服务器端的socket*/ int backlog /*挂起链接队列的最大长度*/ );
好比有100个用户连接请求,可是系统一次只能处理20个,那么剩下的80个不能不理人家,因此系统就建立个队列记录这些暂时不能处理,过一下子处理的连接请求,依前后顺序处理,那这个队列到底多大?就是这个参数设置,好比2,那么就容许两个新连接排队的。这个确定不能无限大,那内存不够了。
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN)) { int a = WSAGetLastError(); WSACleanup(); return 0; }
if (SOCKET_ERROR == listen(socketListen,2)) { printf("listen fail!"); //关闭库 closesocket(socketListen); WSACleanup(); return -1; }
在服务器端上建立一个新的socket,将客户端的信息和新的socket绑定在一个,一次只能建立一个
SOCKET WSAAPI accept ( SOCKET s, /*服务器的socket*/ sockaddr *addr, /*返回客户端地址端口信息结构体*/ int *addrlen /*返回参数2的类型大小*/ );
理解:经过服务器端的socket,读取客户端的信息
/*直接经过函数获得客户端的端口号、IP地址*/ getpeername(newSocket,(struct sockaddr *)&sockClient,&nLen); /*获得本地服务器信息*/ getsockname(sSocket,(struct sockaddr *)&addr,&nLen);
若是参数二、3都填==NULL==,那么就是不直接获得客户端的地址和端口号
if (INVALID_SOCKET == socketClient) { int a = WSAGetLastError(); closesocket(socketServer); WSACleanup(); }
accept() 返回一个新的套接字来和客户端通讯,addr保存了客户端的IP地址和端口号,而s是服务器端的套接字,注意区分。接下来和客户端通讯时,要使用这个新生成的套接字,而不是原来服务器端的套接字。
SOCKET newSocket; newSocket = accept(socketListen, NULL, NULL); if (INVALID_SOCKET == newSocket) { printf("listen fail!" ); //关闭库 closesocket(socketServer); WSACleanup(); }
没有客户端连接就一直卡着
数据的接收都是协议自己在作,也就是socket底层在操做,系统有一段缓冲区,储存着接收到的数据。recv的做用,就是经过socket找到了这个缓冲区,把数据复制放到本身的数组中
int recv ( SOCKET s, /*客户端的socket,每一个客户端对应惟一的socket*/ char *buf, /*数据缓冲区*/ int len, /*数据长度*/ int flags /*读取方式*/ );
解释:由于网络传输的最大单元是1500字节,这是协议规定的
通常是==参数2的字节-1==,把“0”字符串结尾保留下来
读取方式 | 做用 |
---|---|
==0== | 从系统缓冲区读到buf缓冲区,将系统缓冲区的数据删掉,读出来就删 |
MSG_PEEK | 数据复制到buf缓冲区,可是数据不从系统缓冲区删除 |
MSG_OOB | 传输一段数据,再外带加一个额外的特殊数据 |
MSG_WAITTALL | 直到系统缓冲区字节数知足参数3的数目,才开始读取 |
char szRecvBuffer[1500] = {0}; /* 字符数组 */ int nReturnValue = recv(newSocket, szRecvBuffer, sizeof(szRecvBuffer)-1, 0); if (0 == nReturnValue) { //客户端正常关闭 服务端释放Socket continue ; } else if (SOCKET_ERROR == nReturnValue) { //网络中断 printf("客户端中断链接"); continue; } else { //接收到客户端消息 printf("Client Data : %s \n",szRecvBuffer); }
int WSAAPI send ( SOCKET s, /*客户端socket*/ const char *buf, /*发送的字符数组*/ int len, /*发送长度*/ int flags /*发送方式*/ );
发送的时候,协议要进行包装,加上协议信息(包头),链路层14字节,ip头20字节,tcp头20字节,数据结尾还要有状态确认,加起来也几十个字节,因此不能写1500个字节,最多1400字节(或者1024)。
系统会分片处理,分两个包,假设2000字节的包,1400+包头=1500,600+包头=700。那么系统就要分包->打包->发送,客户端接收到要拆包->组合数据。
发送方式 | 做用 |
---|---|
==0== | 默认 |
MSG_OOB | 传输一段数据,再外带加一个额外的特殊数据 |
MSG_DONTROUTE | 数据不该受路由限制 |
if (SOCKET_ERROR == send(socketClient, "abcd", sizeof("abcd"), 0)) { //出错了 int a = WSAGetLastError(); //根据实际状况处理 }
char szSendBuffer[1024]; /*发送字符数组*/ send(newSocket, "repeat over", strlen(szSendBuffer)+1, 0);
做用:客户端连接服务器端,将本机的一个指定的socket链接到一个指定地址的服务器socket上去
理解:connect将在本机和指定服务器间创建一个链接。但实际上,connect操做并不引起网络设备传送任何的数据到对端。它所 作的操做只是经过路由规则和路由表等一些信息,在struct socket结构中填入一些有关对端服务器的信息。这样,之后向对端发送数据报时,就不须要每次进行路由查询等操做以肯定对端地址信息和本地发送接口,应用程序也就不须要每次传入对端地址信息
int WSAAPI connect ( SOCKET s, /*客户端建立的连接服务器的socket*/ const sockaddr *name, /*服务器IP地址端口号结构体*/ int namelen /*sizeof(sockaddr)*/ );
if (SOCKET_ERROR == connect(socketServer, (struct sockaddr *)&serverMsg, sizeof(serverMsg))) { int a = WSAGetLastError(); closesocket(socketServer); WSACleanup(); }
struct sockaddr_in serverMsg; serverMsg.sin_family = AF_INET; /*地址类型*/ serverMsg.sin_port = htons(12345); /*服务器端口*/ serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); /*服务器IP*/ connect(socketServer, (struct sockaddr *)&serverMsg, sizeof(serverMsg)); if (SOCKET_ERROR == connect(socketServer, (struct sockaddr *)&serverMsg, sizeof(serverMsg))) { int a = WSAGetLastError(); closesocket(socketServer); WSACleanup(); }
因为accept() ,recv()是阻塞的,作其中一件事,另一件事就作不了。
咱们能够主动和系统要有请求的socket
答:由于服务器接收、发送数据都是经过绑定客户端信息的socket进行的,不是经过服务器socket,服务器socket只是接受客户端请求的连接,而且把客户端的信息绑定到一个新的socket上,之后的通讯都是经过这个socket,因此服务器有响应就是有新的请求连接
答:客户端所建立的socket只是本机和指定服务器间创建一个链接,socket结构中填入一些有关对端服务器的信息。这样,之后向对端发送数据报时,就不须要每次进行路由查询等操做以肯定对端地址信息和本地发送接口,能够理解为客户端的所建立的socket其实就是和服务器数据交换的socket,与服务器端最开始建立的socket不一样。