整理TCP/IP UDP通信的相关知识点和内容
目录
基于TCP(面向连接)的socket编程
服务器端程序:
- 创建套接字(socket)
- 将套接字绑定到一个本地地址和端口上(bind):目的是为了告诉客户端,服务器准备在哪个ip地址哪个端口接受请求。
- 将套接字设置为监听模式,准备接收客户端请求(listen)
- 等待客户端请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。接收客户端请求后,就保存了客户端的IP和端口号。
- 用返回的套接字 ,与客户端进行通信(send/recv)
- 当不需要的时候,关闭套接字(closesocket)
客户端程序:
- 创建套接字(socket)
- 向服务器发出连接请求(connect)
- 和服务器端进行通信(send/recv)
- 关闭套接字(closesocket)
运行时,服务器端先启动。
基于UDP(面向无连接)的socket编程
服务器端(接收端)程序:
- 创建套接字(socket)
- 将套接字绑定到一个本地地址和端口上(bind):告诉客户端,服务器端在哪个端口哪个IP上等待数据。
- 等待接收数据(recvfrom/sendto)
- 关闭套接字(closescoket)
客户端(发送端)程序:
- 创建套接字(socket)
- 向服务器发送数据(sendto/recvfrom)
- 关闭套接字(closesocket)
运行时,服务器端(接收端)先启动。
以下例子,均是基于VS2010的win32控制台应用程序。
TCP/IP 服务端与客户端示例
TcpServer.cpp如下:属性中,链接里面,如要输入:ws2_32.lib
//#include <Windows.h>// 不能与#include <WinSock2.h>,同时存在。否则会出现函数重定义。
#include <WinSock2.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
// 加载socket库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}
if (LOBYTE(wsaData.wVersion) != 2||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}
// 创建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
// 绑定套接字
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);// 将u_long类型转换为网络字节序
addrSrv.sin_family = AF_INET; // 地址族
addrSrv.sin_port = htons(6000);// 用1024以上的端口号
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
// listen监听
listen(sockSrv, 5);// 等待链接的最大数,设为5
// 接收客户端地址信息
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while (1)
{
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);//返回相对于这个链接的套接字
char sendBuf[100];
sprintf(sendBuf, "welcome %s to connect server!\n",
inet_ntoa(addrClient.sin_addr));
send(sockConn,sendBuf, strlen(sendBuf)+1, 0);
char recvBuf[100];
recv(sockConn, recvBuf, 100, 0);
printf("%s\n", recvBuf);
//这个时候释放的只是这一个客户连接,其它被服务的客户连接可能还存在。
//最重要的是,监听套接字一直都处于“监听”状态,等待新的客户请求到达并服务。
closesocket(sockConn);
}
WSACleanup();
system("pause");
return 0;
}
TcpClient.cpp文件.属性中,链接里面,如要输入:ws2_32.lib
#include <WinSock2.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
// 加载socket库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}
if (LOBYTE(wsaData.wVersion) != 2||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}
// 创建套接字
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
// 发送连接请求
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// 本地回路地址. 地址为u_long类型,需要用inet_addr进行转换
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
// 接收并发送消息,完成一次对话
char recvBuf[100];
recv(sockClient, recvBuf, 100, 0);
printf("%s\n", recvBuf);
send(sockClient, "this is client", sizeof("this is client")+1, 0);
// 最后关闭套接字,清理套接字库
closesocket(sockClient);
WSACleanup();
system("pause");
return 0;
}
服务器处于等待状态,客户端可以开启多个,然后连接到服务器端。
猜想,是不是客户端和服务器端通信之后,可以像聊天一样,你一言我一语的进行交流呢? 那就把客户端 TcpClient.cpp 改一下循环试试,如下:
#include <WinSock2.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
// 加载socket库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}
if (LOBYTE(wsaData.wVersion) != 2||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}
// 创建套接字
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
// 发送连接请求
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// 本地回路地址. 地址为u_long类型,需要用inet_addr进行转换
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
// char recvBuf[100];
// recv(sockClient, recvBuf, 100, 0);
// printf("%s\n", recvBuf);
// send(sockClient, "this is client", sizeof("this is client")+1, 0);
while (1)
{
char recvBuf[100];
recv(sockClient, recvBuf, 100, 0);
printf("server say : %s\n", recvBuf);
send(sockClient, "this is client", sizeof("this is client")+1, 0);
}
closesocket(sockClient);
WSACleanup();
system("pause");
return 0;
}
然后出现了如下问题:客户端会一直接收到服务器端的信息,但是服务器只接收一次客户端信息。可能的原因:服务器只连接一次。也就是对于一个IP,仅接受一次请求。 不能像聊天那样进行。如果需要聊天那样的话,是基于UDP通信,可以实现。
UDP 服务端与客户端示例
UdpServer.cpp, 属性中,链接里面,如要输入:ws2_32.lib
#include <WinSock2.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
// 加载socket库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}
if (LOBYTE(wsaData.wVersion) != 2||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}
// 创建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
// 绑定
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
// 接收
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
char recvBuf[100];
recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len);
printf("%s\n", recvBuf);
closesocket(sockSrv);
WSACleanup();
system("pause");
return 0;
}
UdpClient.cpp.属性中,链接里面,如要输入:ws2_32.lib
#include <WinSock2.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
// 加载socket库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}
if (LOBYTE(wsaData.wVersion) != 2||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}
// 创建套接字
SOCKET client = socket(AF_INET, SOCK_DGRAM, 0);
// 发送
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
sendto(client, "this is client", sizeof("this is client"), 0, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
closesocket(client);
WSACleanup();
system("pause");
return 0;
}
运行结果:
基于UDP的简单的聊天窗口
UdpServer.cpp, 属性中,链接里面,如要输入:ws2_32.lib
#include <WinSock2.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
// 加载socket库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}
if (LOBYTE(wsaData.wVersion) != 2||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}
// 创建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
// 绑定
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
char recvBuf[100];
char sendBuf[100];
char tempBuf[100];
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while (TRUE)
{
recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len);// 先接收数据
if ('q' == recvBuf[0])
{
sendto(sockSrv, "q", strlen("q")+1, 0, (SOCKADDR*)&addrClient, len);
printf("Client Chat end!\n");
//cout << "Client Chat end!" << endl;
break;
}
sprintf(tempBuf, "%s say : %s", inet_ntoa(addrClient.sin_addr), recvBuf);
//cout << inet_ntoa(addrClient.sin_addr) << " say: " << recvBuf << endl;
printf("%s\n", tempBuf);
//cout << tempBuf << endl;
cout << "请输入内容:" << endl;// C++
//cin >> sendBuf;
gets(sendBuf);// 从键盘输入 // C标准输入
sendto(sockSrv, sendBuf, strlen(sendBuf)+1, 0, (SOCKADDR*)&addrClient, len);
}
closesocket(sockSrv);
WSACleanup();
system("pause");
return 0;
}
UdpClient.cpp 文件。属性中,链接里面,如要输入:ws2_32.lib
#include <WinSock2.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main()
{
// 加载socket库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}
if (LOBYTE(wsaData.wVersion) != 2||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}
// 创建套接字
SOCKET client = socket(AF_INET, SOCK_DGRAM, 0);
// 发送
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
char recvBuf[100];
char sendBuf[100];
char tempBuf[100];
int len = sizeof(SOCKADDR);
// 客户端先发送数据
while (TRUE)
{
cout << "请输入内容:" << endl;
gets(sendBuf);
//cin >> sendBuf;
sendto(client, sendBuf, sizeof(sendBuf), 0, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
recvfrom(client, recvBuf, sizeof(recvBuf), 0, (SOCKADDR*)&addrSrv, &len);
if ('q' == recvBuf[0])
{
sendto(client, "q", sizeof("q")+1, 0, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
//printf("Server Chat end!\n");
cout <<"Server Chat end!" << endl;
break;
}
sprintf(tempBuf, "%s say : %s", inet_ntoa(addrSrv.sin_addr), recvBuf);
//cout << inet_ntoa(addrSrv.sin_addr) <<" say : "<< recvBuf << endl;
//printf("%s\n", tempBuf);
cout << tempBuf << endl;
}
closesocket(client);
WSACleanup();
system("pause");
return 0;
}
运行结果:
当我把标准输入输出,直接换为 cin/cout的时候,就出现了神奇的现象,如下:可能哪里不对吧。
过程中,遇到的问题:
为什么获取客户端ip地址总是:204.204.204.204
解决方案:
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));// 判断试一下,绑定是否正确。我这里把第二个参数,误写成了第一个参数,就会出现这个问题。
204换成16进制是0xcc,这是vc初始化堆栈的时候使用的数据。也就是accept没有成专功,所以里面没数据。accept接收失败,会返回-1.
进阶篇
TCP/IP, 采用多线程,实现顺畅聊天。
https://zhuanlan.zhihu.com/p/234491475?utm_source=wechat_session