今天开始进入Windows网络编程的学习,在学习的时候总是陷入Windows复杂的参数,纠结于这些。从老师的讲解中,这些内容属于是定式,主要学习写的逻辑。给自己提个醒,要把精力放在正确的位置,不要无端耗费精力。
C:Client 客户端:
S:Server 服务端:
TCP:面向连接的套接字
UDP:面向消息的套接字
是分配给用户上网使用的网际协议
常见的IP地址分为IPv4与IPv6两大类两大类
为区分程序中创建的套接字而分配给套接字的序号
不同的端口对应不同的应用程序
端口有65536之多
SOCK_STREAM 对应TCP协议
SOCK_DGRAM 对应UDP
SOCK_RAW 可以读写内核没有处理的IP数据包,避开 TCP/IP 处理机制,被传送的数据报可以被直接传送给需要它的的应用程序
两个重要的数据结构:
struct sockaddr {
u_short sa_family; //16 位地址类型 2 字节
char sa_data[14]; //14 字节地址数据:ip + port
};
struct sockaddr_in {
short sin_family; //16 位地址类型
u_short sin_port; //16 位端口号 65535 2 的 16 次方
struct in_addr sin_addr; //32 位 IP 地址 4 字节
char sin_zero[8]; //8 字节填充
};
代码如下:
#include
#include
#include
// error LNK2019: 无法解析的外部符号 __imp__accept@12,函数 _main 中引用了该符号
#pragma comment(lib,"ws2_32.lib") // 导入库
int main()
{
printf("TCP Server\n");
// 0. 初始化网络库 加载套接字库 这是个标准做法,直接拷贝即可
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
// 1、初始化套接字库
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
printf("WSAStartup errorNum = %d\n", GetLastError());
return err;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("LOBYTE errorNum = %d\n", GetLastError());
WSACleanup();
return -1;
}
// 1. 安装电话机
// AF_INET指定了使用 IPv4 地址和 TCP/IP 协议栈进行网络通信。
// 用于指定套接字的类型,表示基于流的传输协议,常用于 TCP 协议的网络通信。
// 第三个参数由前两个决定。
// 光标放在socket()函数上,摁下F1,转到官方文档
SOCKET sockSrv = socket(AF_INET,SOCK_STREAM,0); //socket 函数创建一个绑定到特定传输服务提供者的套接字。
if (INVALID_SOCKET == sockSrv)
{
printf("socket errorNum = %d\n", GetLastError());
return -1;
}
// 2. 分配电话号码,填充参数
SOCKADDR_IN addSrv; // SOCKADDR_IN结构指定 AF_INET 地址系列的传输地址和端口。
// 设置为任何IP
addSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addSrv.sin_family = AF_INET;
addSrv.sin_port = htons(6000);
// 根据官方文档,这里需要强转
if (SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addSrv, sizeof(SOCKADDR))) // 绑定函数将本地地址与套接字相关联。
{
printf("bind errorNum = %d\n", GetLastError());
return -1;
}
// 3. 监听 listen
// 将套接字置于侦听传入连接的状态。
if (SOCKET_ERROR == listen(sockSrv, 5))
{
printf("listen errorNum = %d\n", GetLastError());
return -1;
}
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
while (true)
{
// 4.分配一台分机去处理客户端的连接
printf("begin accept: \n");
SOCKET sockConn = accept(sockSrv,(SOCKADDR*)&addrCli,&len);
printf("end accept: \n");
char sendBuf[100] = { 0 };
sprintf_s(sendBuf, 100, "Welcome %s to bingo!",inet_ntoa(addrCli.sin_addr));
int iLen = send(sockConn,sendBuf,strlen(sendBuf),0);
char recvBuf[100] = { 0 };
iLen = recv(sockConn, recvBuf, 100, 0);
printf("recvBuf = %s\n", recvBuf);
// 关闭分机
closesocket(sockConn);
}
// 关闭总机
closesocket(sockSrv);
WSACleanup();
system("pause");
return 0;
}
代码如下:
#include
#include
#include
#pragma comment(lib, "ws2_32.lib")
int main()
{
printf("Client\n");
char sendBuf[] = "hello,world";
//1 初始化网络库
// 加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
// 1、初始化套接字库
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
printf("WSAStartup errorNum = %d\n", GetLastError());
return err;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("LOBYTE errorNum = %d\n", GetLastError());
WSACleanup();
return -1;
}
// 2 安装电话机
// 新建套接字
SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == sockCli)
{
printf("socket errorNum = %d\n", GetLastError());
return -1;
}
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("10.134.142.81");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
// 3 连接服务器
if (SOCKET_ERROR == connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
{
printf("connect errorNum = %d\n", GetLastError());
return -1;
}
// 4 接收和发送数据
char recvBuf[100] = { 0 };
int iLen = recv(sockCli, recvBuf, 100, 0);
if (iLen < 0)
{
printf("recv errorNum = %d\n", GetLastError());
return -1;
}
printf("Client recvBuf = %s\n", recvBuf);
// 发送数据
iLen = send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
if (iLen < 0)
{
printf("send errorNum = %d\n", GetLastError());
return -1;
}
// 关闭套接字
closesocket(sockCli);
WSACleanup();
system("pause");
return 0;
}
// 报错:严重性 代码 说明 项目 文件 行 禁止显示状态
// 错误 C4996 'inet_addr': Use inet_pton() or InetPton() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings TCP Client C : \Users\mi\Desktop\Code - C++\MyNetPro\TCP Client\TCP Client.cpp 38
// 我们需要把它 _WINSOCK_DEPRECATED 忽略掉 工程属性 C++预处理器添加即可
结果如下:
listen(sockSrv,5):这个函数是规定了消息队列,最多可以有五个,后面再来的消息会来连接不上。
设置哪个为启动项就会编译那个过程的C++文件
// 报错:严重性 代码 说明 项目 文件 行 禁止显示状态
// 错误 C4996 'inet_addr': Use inet_pton() or InetPton() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings TCP Client C : \Users\mi\Desktop\Code - C++\MyNetPro\TCP Client\TCP Client.cpp 38
// 我们需要把它 _WINSOCK_DEPRECATED 忽略掉 工程属性 C++预处理器添加即可
接收端:
int MySocketRecv0(int sock, char* buf, int dateSize)
{
//循环接收
int numsRecvSoFar = 0; // 目前受到的数据
int numsRemainingToRecv = dateSize; // 剩余要收的数据
printf("enter MySocketRecv0\n");
while (1)
{
int bytesRead = recv(sock, &buf[numsRecvSoFar], numsRemainingToRecv, 0);
printf("###bytesRead = %d,numsRecvSoFar = %d, numsRemainingToRecv = %d\n",
bytesRead, numsRecvSoFar, numsRemainingToRecv);
if (bytesRead == numsRemainingToRecv) // 一次性完全接收
{
return 0;
}
else if (bytesRead > 0) // 未接收完,更新已接受数据和未接受数据
{
numsRecvSoFar += bytesRead;
numsRemainingToRecv -= bytesRead;
continue;
}
else if ((bytesRead < 0) && (errno == EAGAIN))
{
continue;
}
else
{
return -1;
}
}
}
发送端:
int MySocketSend0(int socketNum, unsigned char* data, unsigned dataSize)
{
unsigned numBytesSentSoFar = 0;
unsigned numBytesRemainingToSend = dataSize;
while (1)
{
int bytesSend = send(socketNum, (char const*)(&data[numBytesSentSoFar]),
numBytesRemainingToSend, 0/*flags*/);
if (bytesSend == numBytesRemainingToSend)
{
return 0;
}
else if (bytesSend > 0)
{
numBytesSentSoFar += bytesSend;
numBytesRemainingToSend -= bytesSend;
continue;
}
else if ((bytesSend < 0) && (errno == 11))
{
continue;
}
else
{
return -1;
}
}
}