1 tcp/ip 协议的 C/S模型介绍
本文思维导图
1.1 Transmission Control Protocol / Internet Protocol
- 重要性:互联网的基石
- 概念:tcp/ip协议族(簇, 组,体系),并不是tcp协议和ip协议的总称,tcp/ip指的是整个网络传输体系。而tcp协议和ip协议就是单单的两个协议
- 特点:
-
- tcp/ip:面向连接的、可靠的、基于字节流的传输层协议
-
- udp/ip:面向非连接的、不可靠的、基于数据报的传输层协议
1.2 client/server :客户端/服务器模型
应用:QQ、微信、LOL、DNF等客户端
实现层面可以基于任何的网络协议
1.3 browser/server : 浏览器/服务器模型
基于HTTP/HTTPS协议
1.4 套接字编程/socket编程
socket = 套接字 ,统称网络编程
2 网络库与网络头文件
2.1 网络头文件/网络库 版本要对应
server.c
#include <WinSock2.h> //Win -> Windows Sock-> Socket 2-> Verson 2
#pragma comment(lib,"Ws2_32.lib")//第2版的网络库 头文件名/库名->不分大小写
第1版的网络库
//#include <winsock.h> -> Verson1
//#pragma comment(lib,"wsock32.lib")//第1版的网络库
3 WSAStartup()函数详解
MSDN官方文档
The WSAStartup function initiates use of the Winsock DLL by a process.
WSAStartup 函数启动进程对 Winsock DLL 的使用。
支持的版本:
3.1 WSAStartup()参数详解
w windows
s socket
a Asynchronous 异步
startup 启动
打开网络库/启动网络库,启动了这个库,这个库里的函数/功能才能使用
语法
int WSAAPI WSAStartup(
[in] WORD wVersionRequested,
[out] LPWSADATA lpWSAData
);
Parameters
-
[in] wVersionRequested
-
- 调用方可以使用的最高版本的 Windows 套接字规范。高阶字节指定次要版本号;低位字节指定主要版本号。
以版本2.1为例,使用MAKEWORD()函数
- 调用方可以使用的最高版本的 Windows 套接字规范。高阶字节指定次要版本号;低位字节指定主要版本号。
-
[out] lpWSAData
-
- 指向 WSADATA 数据结构的指针,用于接收 Windows 套接字实现的详细信息。
-
系统传给我们的网络信息
-
当看到参数有 LP P前缀的时候,是说我们这里要传对应类型变量的地址,这是win32API 的规范 或者叫规则
//写法①
WSADATA wd_sock_msg;
int ret = WSAStartup(ws_verson, &wd_sock_msg);
//写法②
LPWSADATA lpw = malloc(sizeof(WSADATA));// 后面需要free()
WSADATA* wsa_data = malloc(sizeof(WSADATA));// 后面需要free()
int ret2 = WSAStartup(ws_verson, lpw);
3.2 WSAStartup()返回值介绍
3.2.1如果成功,WSAStartup 函数将返回零。
3.2.2 否则,它将返回下面列出的错误代码之一。
WSAStartup 函数直接在此函数的返回值中返回扩展的错误代码
。不需要调用 WSAGetLastError 函数,也不应使用该函数。
Error code | code | Meaning | Reason | Note |
---|---|---|---|---|
WSASYSNOTREADY | 10091 | The underlying network subsystem is not ready for network communication. | 底层网络子系统尚未准备好进行网络通信。 | 系统配置问题,重启下电脑,检查ws2_32库是否存在,或者是否在环境配置目录下 |
WSAVERNOTSUPPORTED | 10092 | The version of Windows Sockets support requested is not provided by this particular Windows Sockets implementation. | 请求的 Windows 套接字支持版本不是由此特定的 Windows 套接字实现提供的。 | 要使用的版本不支持 |
WSAEINPROGRESS | 10067 | A blocking Windows Sockets 1.1 operation is in progress. | 已达到对Windows套接字实现支持的任务数量的限制。 | Windows Sockets实现可能限制同时使用它的应用程序的数量 |
WSAEPROCLIM | 10036 | A limit on the number of tasks supported by the Windows Sockets implementation has been reached | 正在阻止Windows Sockets 1.1操作。 | 当前函数运行期间,由于某些原因造成阻塞,会返回在这个错误码,其他操作均禁止 |
WSAEFAULT | 10014 | The lpWSAData parameter is not a valid pointer | lpWSAData参数不是有效指针。 | 参数写错了 |
server.c
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h> //Win -> Windows Sock-> Socket 2-> Verson 2
#pragma comment(lib,"Ws2_32.lib")//第2版的网络库 头文件名/库名->不分大小写
int main(int argc, char* argv[])
{
WORD ws_verson = MAKEWORD(2, 2);
WSADATA wd_sock_msg;
int ret = WSAStartup(ws_verson, &wd_sock_msg);
if (0 != ret)
{
switch (ret)
{
case WSASYSNOTREADY:
printf("重启下电脑试试,或者检查网络库!");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库!");
break;
case WSAEPROCLIM:
printf("请重新启动!");
break;
case WSAEINPROGRESS:
printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
break;
case WSAEFAULT:
printf("参数错误!");
break;
default:
break;
}
}
printf("网络库打开成功!");
printf("\n");
system("pause");
return 0;
}
4 版本校验
LOWORD, HIWORD, LOBYTE, HIBYTE的理解
server.c
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h> //Win -> Windows Sock-> Socket 2-> Verson 2
#pragma comment(lib,"Ws2_32.lib")//第2版的网络库 头文件名/库名->不分大小写
int main(int argc, char* argv[])
{
WORD ws_verson = MAKEWORD(2, 2);
WSADATA wd_sock_msg;
int ret = WSAStartup(ws_verson, &wd_sock_msg);
if (0 != ret)
{
switch (ret)
{
case WSASYSNOTREADY:
printf("重启下电脑试试,或者检查网络库!");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库!");
break;
case WSAEPROCLIM:
printf("请重新启动!");
break;
case WSAEINPROGRESS:
printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
break;
case WSAEFAULT:
printf("参数错误!");
break;
default:
break;
}
return 0;
}
printf("网络库打开成功!");
/*=====网络库版本号版本校验=====*/
if (2 != LOBYTE(wd_sock_msg.wVersion) || //地位-》得到主版本
2 != HIBYTE(wd_sock_msg.wVersion)) //高位-》得到副版本
{
//说明版本不对,要关闭网络库
WSACleanup();
return 0;
}
printf("网络库版本号:(%d,%d)", LOBYTE(wd_sock_msg.wVersion), HIBYTE(wd_sock_msg.wVersion));
printf("\n");
system("pause");
return 0;
}
5 什么是socket
将底层复杂的协议体系,执行流程,进行了封装,封装完的结果,就是一个SOCKET了。也就是说,SOCKET是我们调用协议进行通信的 操作接口
- 意义: 将复杂的协议过程与我们编程人员分开,我们直接操作一个简单SOCKET就行了,对于底层的协议 过程细节,完全不用知道,这就大大方便了我们。
本质
:就是一种数据类型
,转定义看下类型- 就是一个整数, uint
- 但是这个数是唯一的:标识着我当前的应用程序,协议特点等信息
5.1 socket() 参数介绍
The socket function creates a socket that is bound to a specific transport service provider.
套接字函数创建绑定到特定传输服务提供程序的套接字。
语法
SOCKET WSAAPI socket(
[in] int af,//地址的类型
[in] int type,//套接字类型
[in] int protocol//协议的类型
);
5.1.1 [in] int af 地址的类型
地址系列规范。地址系列的可能值在 Winsock2.h 头文件中定义。
Af | code | Meaning | - |
---|---|---|---|
AF_UNSPEC | 0 | The address family is unspecified. | - |
AF_INET | 2 | The Internet Protocol version 4 (IPv4) address family. | Internet协议版本4(IPv4)地址系列。 |
AF_IPX | 6 | The IPX/SPX address family. This address family is only supported if the NWLink IPX/SPX NetBIOS Compatible Transport protocol is installed.This address family is not supported on Windows Vista and later. | Internet协议版本6(IPv6)地址系列。 |
5.1.2 [in] int type 套接字类型
新套接字的类型规范。
Type | code | Meaning | - |
---|---|---|---|
SOCK_STREAM | 1 | A socket type that provides sequenced, reliable, two-way, connection-based byte streams with an OOB data transmission mechanism. This socket type uses the Transmission Control Protocol (TCP) for the Internet address family (AF_INET or AF_INET6). | 一种套接字类型,提供带有OOB数据传输机制的顺序,可靠,双向,基于连接的字节流。 此套接字类型使用传输控制协议(TCP)作为Internet地址系列(AF_INET或AF_INET6)。 |
SOCK_DGRAM | 2 | A socket type that supports datagrams, which are connectionless, unreliable buffers of a fixed (typically small) maximum length. This socket type uses the User Datagram Protocol (UDP) for the Internet address family (AF_INET or AF_INET6). | 一种支持数据报的套接字类型,它是固定(通常很小)最大长度的无连接,不可靠的缓冲区。 此套接字类型使用用户数据报协议(UDP)作为Internet地址系列(AF_INET或AF_INET6)。 |
5.1.3 [in] int protocol 协议的类型
要使用的协议。协议参数的可能选项特定于指定的地址系列和套接字类型。该协议的可能值在 Winsock2.h 和 Wsrm.h 头文件中定义。
Type | code | Meaning | - |
---|---|---|---|
IPPROTO_TCP | 6 | The Transmission Control Protocol (TCP). This is a possible value when the af parameter is AF_INET or AF_INET6 and the type parameter is SOCK_STREAM. | 传输控制协议(TCP)。 当af参数为AF_INET或AF_INET6且类型参数为SOCK_STREAM时,这是一个可能的值。 |
IPPROTO_UDP | 17 | The User Datagram Protocol (UDP). This is a possible value when the af parameter is AF_INET or AF_INET6 and the type parameter is SOCK_DGRAM. | 用户数据报协议(UDP)。 当af参数为AF_INET或AF_INET6且类型参数为SOCK_DGRAM时,这是一个可能的值。 |
5.2 socket()函数返回值介绍
如果未发生错误,套接字将返回引用新套接字的描述符。否则,将返回值 INVALID_SOCKET,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。
成功返回可用的socket
- 不用了就一定要销毁套接字
- closesocket(socketListen);
失败返回INVALID_SOCKET - 关闭网络库
- WSACleanup();
- 可用WSAGetLasterror()返回错误码
server.c
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h>
#pragma comment(lib,"Ws2_32.lib")
int main(int argc, char* argv[])
{
WORD ws_verson = MAKEWORD(2, 2);
WSADATA wd_sock_msg;
int ret = WSAStartup(ws_verson, &wd_sock_msg);
if (0 != ret)
{
switch (ret)
{
case WSASYSNOTREADY:
printf("重启下电脑试试,或者检查网络库!");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库!");
break;
case WSAEPROCLIM:
printf("请重新启动!");
break;
case WSAEINPROGRESS:
printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
break;
case WSAEFAULT:
printf("参数错误!");
break;
default:
break;
}
return 0;
}
printf("网络库打开成功!");
if (2 != LOBYTE(wd_sock_msg.wVersion) || //地位-》得到主版本
2 != HIBYTE(wd_sock_msg.wVersion)) //高位-》得到副版本
{
//说明版本不对,要关闭网络库
WSACleanup();
return 0;
}
printf("网络库版本号:(%d,%d)\n", LOBYTE(wd_sock_msg.wVersion), HIBYTE(wd_sock_msg.wVersion));
//SOCKET -> 类型 socket() -> 函数
SOCKET socket_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == socket_server)
{
printf("socket创建失败,错误码:%d", WSAGetLastError());
return 0;
}
printf("socket创建成功,返回码:%d", socket_server);
/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
closesocket(socket_server);
WSACleanup();//关闭网络库
printf("\n");
system("pause");
return 0;
}
6 bind()函数功能以及参数介绍
绑定函数将本地地址与套接字相关联。
绑定地址与端口
作用:给我们的socket绑定端口号与具体地址
The bind function associates a local address with a socket.
语法
int WSAAPI bind(
[in] SOCKET s,//A descriptor identifying an unbound socket.
[in] const sockaddr *name,//A pointer to a sockaddr structure of the local address to assign to the bound socket .
[in] int namelen
);
6.1 bind()函数参数
6.1.1 [in] SOCKET s
标识未绑定套接字的描述符。
6.1.2 [in] const sockaddr *name
指向要分配给绑定套接字的本地地址的 sockaddr 结构的指针。
struct sockaddr {
ushort sa_family; //2字节
char sa_data[14];//14字节
};
struct sockaddr_in {
short sin_family;//2字节
u_short sin_port;//2字节
struct in_addr sin_addr;//4字节
char sin_zero[8];//8字节
};
sockaddr 和 sockaddr_in 两个结构体占的内存一样,但是sockaddr 不方便赋值,因此,先用sockaddr_in 赋值,再强转成sockaddr
端口号范围0-65535
实际上,0-1023为系统保留
- 21 FTP
- 25 SMTP
- 80 HTTP
查看端口是否被占用
-
cmd netstat -ano 查看被使用的所有端口
-
-
netstat -ano|findstr “12345”
-
- 显示了-》被使用
-
-
- 未显示-》未使用
- 未显示-》未使用
6.1.3 [in] int namelen
name 参数所指向的值的长度(以字节为单位)。
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");
bind(socket_server, (const struct sockaddr *) &si, sizeof(si));
6.2 bind()函数返回值
如果未发生错误,则绑定返回零。否则,它将返回SOCKET_ERROR,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。
server.c
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h> //Win -> Windows Sock-> Socket 2-> Verson 2
#pragma comment(lib,"Ws2_32.lib")//第2版的网络库 头文件名/库名->不分大小写
int main(int argc, char* argv[])
{
WORD ws_verson = MAKEWORD(2, 2);
WSADATA wd_sock_msg;
int ret = WSAStartup(ws_verson, &wd_sock_msg);
if (0 != ret)
{
switch (ret)
{
case WSASYSNOTREADY:
printf("重启下电脑试试,或者检查网络库!");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库!");
break;
case WSAEPROCLIM:
printf("请重新启动!");
break;
case WSAEINPROGRESS:
printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
break;
case WSAEFAULT:
printf("参数错误!");
break;
default:
break;
}
return 0;
}
printf("网络库打开成功!\n");
if (2 != LOBYTE(wd_sock_msg.wVersion) || //地位-》得到主版本
2 != HIBYTE(wd_sock_msg.wVersion)) //高位-》得到副版本
{
//说明版本不对,要关闭网络库
WSACleanup();
return 0;
}
printf("网络库版本号:(%d,%d)\n", LOBYTE(wd_sock_msg.wVersion), HIBYTE(wd_sock_msg.wVersion));
SOCKET socket_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == socket_server)
{
printf("socket created failed,error_code:%d\n", WSAGetLastError());
return 0;
}
printf("socket created success,return code:%d\n", socket_server);
/*=============绑定==========*/
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");
int res_bind = bind(socket_server, (const struct sockaddr *) &si, sizeof(si));
if (SOCKET_ERROR == res_bind)
{
printf("bind failed,error_code:%d", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return 0;
}
printf("bind success,error_code:%d", res_bind);
/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
closesocket(socket_server);
WSACleanup();//关闭网络库
printf("\n");
system("pause");
return 0;
}
htons函数
uint16_t htons(uint16_t hostint16);
功能:
将16位主机字节序数据转换成网络字节序数据
参数:
uint16_t:unsigned short int
hostint16:待转换的16位主机字节序数据
返回值:
成功:返回网络字节序的值
头文件:
#include <arpa/inet.h>
inet_addr()函数
//inet_addr方法可以转化字符串,主要用来将一个十进制的数转化为二进制的数,用途多于ipv4的IP转化
//将一个点分十进制的IP转换成一个长整型数(u_long类型)等同于inet_addr()。
in_addr_t inet_addr(const char* strptr);
//返回:若字符串有效则将字符串转换为32位二进制网络字节序的IPV4地址,否则为INADDR_NONE
struct in_addr{
in_addr_t s_addr;
}
//所处头文件: #include <arpa/inet.h>
//例子:
daddr.s_addr=inet_addr("192.168.1.60");
7 listen()函数
The listen function places a socket in a state in which it is listening for an incoming connection.
将套接字置于正在侦听传入连接的状态。
语法
int WSAAPI listen(
[in] SOCKET s,//服务器端的socket,也就是socket函数创建的
[in] int backlog//挂起连接队列的最大长度。
);
7.1 listen()参数
listen 函数将套接字置于侦听传入连接的状态。
语法
int WSAAPI listen(
[in] SOCKET s,
[in] int backlog
);
7.1.1 [in] SOCKET s
标识绑定的、未连接的套接字的描述符。
7.1.2 [in] int backlog
The maximum length of the queue of pending connections.
挂起连接队列的最大长度。
我们可以手动设置这个参数,但是别大了。可能2-10个,~20多。
挂起连接队列的最大长度。如果设置为 SOMAXCONN,
则负责套接字的基础服务提供商会将积压工作设置为最大合理值。如果设置为 SOMAXCONN_HINT(N)(其中 N 是一个数字),则积压工作 (backlog) 值将为 N,调整为在范围 (200, 65535) 内。请注意,SOMAXCONN_HINT可用于将积压工作设置为比 SOMAXCONN 更大的值。
作用是让系统自动选择最合适的个数
7.1.3 WSAAPI 调用约定
- 函数名字的编译方式
- 参数的入栈顺序
- 函数的调用时间
7.2返回值
If no error occurs, listen returns zero.
Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.
server.c
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h> //Win -> Windows Sock-> Socket 2-> Verson 2
#pragma comment(lib,"Ws2_32.lib")//第2版的网络库 头文件名/库名->不分大小写
int main(int argc, char* argv[])
{
/*打开网络库****************************************************************/
WORD ws_verson = MAKEWORD(2, 2);
WSADATA wd_sock_msg;
int ret = WSAStartup(ws_verson, &wd_sock_msg);
if (0 != ret)
{
switch (ret)
{
case WSASYSNOTREADY:
printf("重启下电脑试试,或者检查网络库!");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库!");
break;
case WSAEPROCLIM:
printf("请重新启动!");
break;
case WSAEINPROGRESS:
printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
break;
case WSAEFAULT:
printf("参数错误!");
break;
default:
break;
}
}
printf("网络库打开成功!\n");
/*检查版本******************************************************************/
if (2 != LOBYTE(wd_sock_msg.wVersion) || //地位-》得到主版本
2 != HIBYTE(wd_sock_msg.wVersion)) //高位-》得到副版本
{
//说明版本不对,要关闭网络库
WSACleanup();
return 0;
}
printf("网络库版本号:(%d,%d)\n", LOBYTE(wd_sock_msg.wVersion), HIBYTE(wd_sock_msg.wVersion));
/*创建套接字****************************************************************/
SOCKET socket_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == socket_server)
{
printf("socket created failed,error_code:%d\n", WSAGetLastError());
return 0;
}
printf("socket created success,return code:%d\n", socket_server);
/*绑定*********************************************************************/
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");
int res_bind = bind(socket_server, (const struct sockaddr *) &si, sizeof(si));
if (SOCKET_ERROR == res_bind)
{
printf("bind failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return 0;
}
printf("bind success,return code:%d\n", res_bind);
/*监听listen***************************************************************/
int res_listen = listen(socket_server, SOMAXCONN);
if (SOCKET_ERROR == res_listen)
{
printf("listen failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return 0;
}
printf("listen success,return code:%d\n", res_listen);
/*关闭socket,关闭网络库*****************************************************/
/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
closesocket(socket_server);
WSACleanup();//关闭网络库
printf("\n");
system("pause");
return 0;
}
8 accept()函数
The accept function permits an incoming connection attempt on a socket.
accept函数允许在套接字上进行传入连接尝试。
本质作用:将客户端的信息创建成SOCKET
listen监听客户端来的链接,accept讲客户端的信息绑定到一个socket上,也就是给客户端创建一个socket,通过返回值返回给我们客户端的socket
一次只能创建一个,有几个客户端链接,就要调用几次
8.1 accept()函数参数
8.1.1 [in] s
A descriptor that identifies a socket that has been placed in a listening state with the listen function. The connection is actually made with the socket that is returned by accept.
我们上面创建的自己的服务端socket
socket先处于监听状态,然后来的链接都在由这个管理,我们取客户端的信息,就是通过这个我们自己的socket
8.1.2 [out]
addr
An optional pointer to a buffer that receives the address of the connecting entity, as known to the communications layer. The exact format of the addr parameter is determined by the address family that was established when the socket from the sockaddr structure was created.
客户端的地址端口信息结构体
意义:系统帮我们监视着客户端的动态,肯定会记录客户端的信息,也就是IP地址,和端口号,并通过这个结构体记录
- 参数2 3也能都设置成NULL,那就是不直接得到客户端的地址,端口号咯,此时可以通过getpeername()函数得到客户端信息
getpeername(newSocket, (struct sockaddr*)&sockClient, &nLen);
- 得到本地服务器信息
getsockname(sSocket, (sockaddr*)&addr, &nLen);
8.1.3 [in, out]
addrlen
An optional pointer to an integer that contains the length of structure pointed to by the addr parameter.
一个可选指针,指向接收通信层已知的连接实体地址的缓冲区。addr 参数的确切格式由创建 sockaddr 结构中的套接字时建立的地址系列确定。
server.c
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h> //Win -> Windows Sock-> Socket 2-> Verson 2
#pragma comment(lib,"Ws2_32.lib")//第2版的网络库 头文件名/库名->不分大小写
int main(int argc, char* argv[])
{
/*打开网络库****************************************************************/
WORD ws_verson = MAKEWORD(2, 2);
WSADATA wd_sock_msg;
int ret = WSAStartup(ws_verson, &wd_sock_msg);
if (0 != ret)
{
switch (ret)
{
case WSASYSNOTREADY:
printf("重启下电脑试试,或者检查网络库!");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库!");
break;
case WSAEPROCLIM:
printf("请重新启动!");
break;
case WSAEINPROGRESS:
printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
break;
case WSAEFAULT:
printf("参数错误!");
break;
default:
break;
}
return 0;
}
printf("网络库打开成功!\n");
/*检查版本******************************************************************/
if (2 != LOBYTE(wd_sock_msg.wVersion) || //地位-》得到主版本
2 != HIBYTE(wd_sock_msg.wVersion)) //高位-》得到副版本
{
//说明版本不对,要关闭网络库
WSACleanup();
return 0;
}
printf("网络库版本号:(%d,%d)\n", LOBYTE(wd_sock_msg.wVersion), HIBYTE(wd_sock_msg.wVersion));
/*创建套接字****************************************************************/
SOCKET socket_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == socket_server)
{
printf("socket created failed,error_code:%d\n", WSAGetLastError());
return 0;
}
else
printf("socket created success,return code:%d\n", socket_server);
/*绑定*********************************************************************/
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");
int res_bind = bind(socket_server, (const struct sockaddr *) &si, sizeof(si));
if (SOCKET_ERROR == res_bind)
{
printf("bind failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return 0;
}
else
printf("bind success,return code:%d\n", res_bind);
/*监听listen***************************************************************/
int res_listen = listen(socket_server, SOMAXCONN);
if (SOCKET_ERROR == res_listen)
{
printf("listen failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return 0;
}
else
printf("listen success,return code:%d\n", res_listen);
/*创建客户端链接accept******************************************************/
struct sockaddr_in client_msg;
int client_len = 0;
SOCKET socket_client = accept(socket_server, (struct sockaddr *)&client_msg, &client_len);
if (INVALID_SOCKET == socket_client)
{
printf("accept failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return 0;
}
else
printf("accept success,return code:%d\n", socket_client);
/*关闭socket,关闭网络库*****************************************************/
/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
closesocket(socket_client);
closesocket(socket_server);
WSACleanup();//关闭网络库
printf("\n");
system("pause");
return 0;
}
8.2 accept()函数特点
1.阻塞、同步
这个函数是阻塞的,没有客户端链接,那就一直卡在这儿,等着。
2.多个链接
一次只能一个,5个就要5次循环
9 recv()函数
The recv function receives data from a connected socket or a bound connectionless socket.
得到指定客户端(参数1)发来的消息
本质:复制
语法
int WSAAPI recv(
[in] SOCKET s,
[out] char *buf,
[in] int len,
[in] int flags
);
9.1recv()函数参数
得到指定客户端(参数1)发来的消息
本质:复制
- 数据的接收都是由协议本身做的,也就是socket的底层做的,
系统会有一段缓冲区,存储着接收到的数据。
调用recv的作用,就是通过socket找到这个缓冲区,并把数据复制进参数2、参数3
9.1.1 [in] SOCKET s
客户端的socket
,每个客户端对应唯一的socket
9.1.2 [out] char *buf
客户端消息的存储空间,也就是个字符数组
这个一般1500字节
- 网络传输得最大单元,1500字节,也就是客户端发过来得数据,一次最大就是1500字节,这是协议规定,这个数值也是根据很多情况,总结出来得最优值
9.1.3 [in] int len
想要读取得字节个数
一般是参数2的字节数-1,把\0字符串结尾留出来
9.1.4 [in] int flags
数据的读取方式
方式 | 解析 |
---|---|
0 | 读出来的就删除 |
MSG_PEEK | 查看传入的数据。数据将复制到缓冲区中,但不会从输入队列中删除。 |
MSG_OOB | 处理带外 (OOB) 数据。 |
MSG_WAITALL | 仅当发生以下事件之一时,接收请求才会完成: + 调用方提供的缓冲区已完全满。 + 连接已关闭。 + 请求已取消或发生错误。 |
推荐填0
9.2 recv()函数返回值
如果未发生错误,accept 将返回 SOCKET 类型的值,该值是新套接字的描述符。此返回值是在其上进行实际连接的套接字的句柄。
否则,将返回值 INVALID_SOCKET,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。
server.c
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h> //Win -> Windows Sock-> Socket 2-> Verson 2
#pragma comment(lib,"Ws2_32.lib")//第2版的网络库 头文件名/库名->不分大小写
int main(int argc, char* argv[])
{
/*打开网络库****************************************************************/
WORD ws_verson = MAKEWORD(2, 2);
WSADATA wd_sock_msg;
int ret = WSAStartup(ws_verson, &wd_sock_msg);
if (0 != ret)
{
switch (ret)
{
case WSASYSNOTREADY:
printf("重启下电脑试试,或者检查网络库!");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库!");
break;
case WSAEPROCLIM:
printf("请重新启动!");
break;
case WSAEINPROGRESS:
printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
break;
case WSAEFAULT:
printf("参数错误!");
break;
default:
break;
}
return 0;
}
printf("网络库打开成功!\n");
/*检查版本******************************************************************/
if (2 != LOBYTE(wd_sock_msg.wVersion) || //地位-》得到主版本
2 != HIBYTE(wd_sock_msg.wVersion)) //高位-》得到副版本
{
//说明版本不对,要关闭网络库
WSACleanup();
return -1;
}
printf("网络库版本号:(%d,%d)\n", LOBYTE(wd_sock_msg.wVersion), HIBYTE(wd_sock_msg.wVersion));
/*创建套接字****************************************************************/
SOCKET socket_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == socket_server)
{
printf("socket created failed,error_code:%d\n", WSAGetLastError());
return -1;
}
else
printf("socket created success,return code:%d\n", socket_server);
/*绑定*********************************************************************/
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");
int res_bind = bind(socket_server, (const struct sockaddr *) &si, sizeof(si));
if (SOCKET_ERROR == res_bind)
{
printf("bind failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return -1;
}
else
printf("bind success,return code:%d\n", res_bind);
/*监听listen***************************************************************/
int res_listen = listen(socket_server, SOMAXCONN);
if (SOCKET_ERROR == res_listen)
{
printf("listen failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return -1;
}
else
printf("listen success,return code:%d\n", res_listen);
/*创建客户端链接accept******************************************************/
struct sockaddr_in client_msg;
int client_len = 0;
SOCKET socket_client = accept(socket_server, (struct sockaddr *)&client_msg, &client_len);
if (INVALID_SOCKET == socket_client)
{
printf("客户端链接 accept failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return -1;
}
else
printf("客户端链接 accept success,return code:%d\n", socket_client);
/*接收客户端消息recv********************************************************/
char buf[1500] = { 0 };
int res_recv = recv(socket_client, buf, 1499, 0);
if (INVALID_SOCKET == res_recv)
{
printf("接收客户端消息 recv failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return -1;
}
else if (0 == res_recv)
{
printf("连接中断,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return 0;
}
else
printf("接收客户端消息 recv success,return code:%d\n buf = %s", res_recv, buf);
/*关闭socket,关闭网络库*****************************************************/
/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
closesocket(socket_client);
closesocket(socket_server);
WSACleanup();//关闭网络库
printf("\n");
system("pause");
return 0;
}
10 send()函数
send 函数在连接的套接字上发送数据。
语法
int WSAAPI send(
[in] SOCKET s,
[in] const char *buf,
[in] int len,
[in] int flags
);
10.1 send()函数参数
10.1.1 [in] SOCKET s
标识连接套接字的描述符。
10.1.2 [in] const char *buf
指向包含要传输的数据的缓冲区的指针。
10.1.3 [in] int len
buf 参数所指向的缓冲区中数据的长度(以字节为单位)。
10.1.4 [in] int flags
一组标志,指定进行调用的方式。此参数是使用具有以下任一值的按位 OR 运算符构造的。
值 | 意义 |
---|---|
MSG_DONTROUTE | 指定数据不应受路由约束。Windows 套接字服务提供商可以选择忽略此标志。 |
MSG_OOB | 仅发送 OOB 数据(流式套接字,如SOCK_STREAM。 |
10.2 send()函数返回值
如果未发生错误,则 send 将返回发送的总字节数,该字节数可能小于 len 参数中请求发送的字节数。否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。
server.c
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h> //Win -> Windows Sock-> Socket 2-> Verson 2
#pragma comment(lib,"Ws2_32.lib")//第2版的网络库 头文件名/库名->不分大小写
int main(int argc, char* argv[])
{
/*打开网络库****************************************************************/
WORD ws_verson = MAKEWORD(2, 2);
WSADATA wd_sock_msg;
int ret = WSAStartup(ws_verson, &wd_sock_msg);
if (0 != ret)
{
switch (ret)
{
case WSASYSNOTREADY:
printf("重启下电脑试试,或者检查网络库!");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库!");
break;
case WSAEPROCLIM:
printf("请重新启动!");
break;
case WSAEINPROGRESS:
printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
break;
case WSAEFAULT:
printf("参数错误!");
break;
default:
break;
}
return 0;
}
printf("网络库打开成功!\n");
/*检查版本******************************************************************/
if (2 != LOBYTE(wd_sock_msg.wVersion) || //地位-》得到主版本
2 != HIBYTE(wd_sock_msg.wVersion)) //高位-》得到副版本
{
//说明版本不对,要关闭网络库
WSACleanup();
return -1;
}
printf("网络库版本号:(%d,%d)\n", LOBYTE(wd_sock_msg.wVersion), HIBYTE(wd_sock_msg.wVersion));
/*创建套接字****************************************************************/
SOCKET socket_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == socket_server)
{
printf("socket created failed,error_code:%d\n", WSAGetLastError());
return -1;
}
else
printf("socket created success,return code:%d\n", socket_server);
/*绑定*********************************************************************/
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");
int res_bind = bind(socket_server, (const struct sockaddr *) &si, sizeof(si));
if (SOCKET_ERROR == res_bind)
{
printf("bind failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return -1;
}
else
printf("bind success,return code:%d\n", res_bind);
/*监听listen***************************************************************/
int res_listen = listen(socket_server, SOMAXCONN);
if (SOCKET_ERROR == res_listen)
{
printf("listen failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return -1;
}
else
printf("listen success,return code:%d\n", res_listen);
/*创建客户端链接accept******************************************************/
struct sockaddr_in client_msg;
int client_len = 0;
SOCKET socket_client = accept(socket_server, (struct sockaddr *)&client_msg, &client_len);
if (INVALID_SOCKET == socket_client)
{
printf("客户端链接 accept failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();//关闭网络库
return -1;
}
else
printf("客户端链接 accept success,return code:%d\n", socket_client);
/*接收客户端消息recv********************************************************/
char rbuf[1500] = { 0 };
int res_recv = recv(socket_client, rbuf, 1499, 0);
if (INVALID_SOCKET == res_recv)
{
printf("接收客户端消息 recv failed,error_code:%d\n", WSAGetLastError());
//根据实际情况处理
return -1;
}
else if (0 == res_recv)
{
printf("连接中断,error_code:%d\n", res_recv);
//根据实际情况处理
return 0;
}
else
printf("接收客户端消息 recv success,return code:%d\n buf = %s", WSAGetLastError(), rbuf);
/*向客户端发送消息 send********************************************************/
char sbuf[1500] = { 0 };
int res_send = send(socket_client, sbuf, sizeof(sbuf), 0);
if (SOCKET_ERROR == res_send)
{
printf("向客户端发送消息 send failed,error_code:%d\n", WSAGetLastError());
//根据实际情况处理
return -1;
}
else
printf("向客户端发送消息 send success,return code:%d\n buf = %s", res_send, rbuf);
/*关闭socket,关闭网络库*****************************************************/
/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
closesocket(socket_client);
closesocket(socket_server);
WSACleanup();//关闭网络库
printf("\n");
system("pause");
return 0;
}
11 客户端代码
client.c
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h>
#pragma comment(lib,"Ws2_32.lib")
int main(int argc, char* argv[])
{
/*打开网络库****************************************************************/
WORD ws_verson = MAKEWORD(2, 2);
WSADATA wd_sock_msg;
int ret = WSAStartup(ws_verson, &wd_sock_msg);
if (0 != ret)
{
switch (ret)
{
case WSASYSNOTREADY:
printf("重启下电脑试试,或者检查网络库!");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库!");
break;
case WSAEPROCLIM:
printf("请重新启动!");
break;
case WSAEINPROGRESS:
printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
break;
case WSAEFAULT:
printf("参数错误!");
break;
default:
break;
}
return 0;
}
printf("网络库打开成功!\n");
/*检查版本******************************************************************/
if (2 != LOBYTE(wd_sock_msg.wVersion) || //地位-》得到主版本
2 != HIBYTE(wd_sock_msg.wVersion)) //高位-》得到副版本
{
//说明版本不对,要关闭网络库
WSACleanup();
return -1;
}
printf("网络库版本号:(%d,%d)\n", LOBYTE(wd_sock_msg.wVersion), HIBYTE(wd_sock_msg.wVersion));
/*创建服务器套接字**********************************************************/
SOCKET socket_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == socket_server)
{
printf("socket created failed,error_code:%d\n", WSAGetLastError());
return -1;
}
else
printf("socket created success,return code:%d\n", socket_server);
/*连接服务器**********************************************************/
struct sockaddr_in server_msg;
server_msg.sin_family = AF_INET;
server_msg.sin_port = htons(12345);
server_msg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int rst_connect = connect(socket_server, (struct sockaddr*)&server_msg, sizeof(server_msg));
if (SOCKET_ERROR == rst_connect)
{
printf("connect server failed,error_code:%d\n", WSAGetLastError());
closesocket(socket_server);
WSACleanup();
return 0;
}
else
printf("connect server success,return code:%d\n", rst_connect);
/*接收服务端消息recv********************************************************/
char rbuf[1500] = { 0 };
int res_recv = recv(socket_server, rbuf, 1499, 0);
if (INVALID_SOCKET == res_recv)
{
printf("接收服务端消息 recv failed,error_code:%d\n", WSAGetLastError());
//根据实际情况处理
return -1;
}
else if (0 == res_recv)
{
printf("连接中断,error_code:%d\n", res_recv);
//根据实际情况处理
return 0;
}
else
printf("接收服务端消息 recv success,return code:%d\n buf = %s", WSAGetLastError(), rbuf);
/*向服务端发送消息 send********************************************************/
char sbuf[1500] = {"hello"};
int res_send = send(socket_server, sbuf, sizeof(sbuf), 0);
if (SOCKET_ERROR == res_send)
{
printf("向服务端发送消息 send failed,error_code:%d\n", WSAGetLastError());
//根据实际情况处理
return -1;
}
else
printf("向服务端发送消息 send success,return code:%d\n buf = %s", res_send, rbuf);
/*关闭socket,关闭网络库*****************************************************/
/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
closesocket(socket_server);
WSACleanup();//关闭网络库
printf("\n");
system("pause");
return 0;
}