全双工通信。本文在前文单双共通信模式下(连接:计算机网络系统学习精华总结(三):传输层——3(套接字编程实战,半双工通信)),采用简单的多线程编程实现,代码几乎每句都包含注释。
服务端代码:
#include
#include
#include //WSAADATA 关键字
#include
#pragma comment (lib, "ws2_32.lib")
#pragma warning(disable : 4996)
/*全双工通信,互不干扰*/
//---------------通信用-----------------------
const int bufLen = 1024;
char reciveBuf[bufLen]; //存放接收数据,字符数组
char sendBuf[bufLen]; //存放发送数据
//---------------通信用-----------------------
using namespace std;
WSAEVENT bExit = WSACreateEvent();//设置一个多线程停止事件
//接收线程函数
DWORD WINAPI recvMessage(LPVOID parsock)
{
SOCKET recvsock = (SOCKET) parsock; //对端客户机传来的套接字
int err; //错误代码
while (true)
{
memset(reciveBuf,0,sizeof(reciveBuf));//初始化接收缓存
if (recv(recvsock, reciveBuf, bufLen, 0) != SOCKET_ERROR)
{//接收成功
cout << "收到内容:" << reciveBuf << endl;
if (strcmp(reciveBuf, "bye") == 0)
{//客户端结束通信,输入bye
SetEvent(bExit);//触发事件,结束线程,继续执行主进程
break;
}
}
else
{//出错了
err = WSAGetLastError();
if (err == WSAECONNRESET)
{//连接有问题了
cout << "The peer is closed" << endl;
continue;//继续下一次
}
SetEvent(bExit);//f否则就是事件触发直接退出
break;
}
}
closesocket(recvsock);//关闭套接字
WSACleanup();//释放资源
return 0;
}
//发送线程函数
DWORD WINAPI sendMessage(LPVOID parsock)
{
SOCKET sendsock = (SOCKET)parsock; //对端客户机传来的套接字
while (true)
{
cout << "请输入要发送的信息:" << endl;
cin.getline(sendBuf, sizeof(sendBuf));
send(sendsock, sendBuf, sizeof(sendBuf),0);//发送消息
if (strcmp(sendBuf, "bye") == 0)
{
SetEvent(bExit);//事件触发
break;
}
}
closesocket(sendsock);
WSACleanup();//释放资源
return 0;
}
int main()
{
//初始化套接字动态库,使用socket2.0版本,
WSADATA wsaDATA;
//WSAStartup(MAKEWORD(2, 0), &wsaDATA);
if (WSAStartup(MAKEWORD(2, 0), &wsaDATA) != 0)
{//初始化失败
std::cout << "初始化失败" << std::endl;
return -1;
}
//创建套接字,int socket(int domain,int type,int protocol)
/*socket函数第一个参数——地址族(协议族、协议域):底层使用哪种通信协议来传递数据。AF_INET使用TCP/IPv4,AF_INET6使用TCP/IPv6,AF_LOCAL或者AF_UNIX指本地通信,当前主机上不同进程间的通信,一般用绝对路径指明
socket函数第二个参数——类型:socket类型有三种,SOCK_STREAM:即TCP,面向字节流,需要先连接,可靠传输,全双工,面向字节流,有流量控制。
SOCK_DGRAM:即UDP,面向数据报,无连接,不可靠,无拥塞控制,首部开销小
SOCK_RAW:即IP,工作在网络层,无连接,不保证数据完整性和有序性,无流量控制。
socket函数第三个参数——协议:常用协议,为0表示默认匹配相应的,IPPROTO_TCP\IPPTOTO_UDP\IPPROTO_SCTP\IPPROTO_TIPC,
其中type与protocol不能任意组合
*/
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);//创建套接字,使用ipv4,TCP协议
if (serverSocket == INVALID_SOCKET)
{
std::cout << "socket创建失败" << std::endl;
WSACleanup();//释放套接字资源
return -1;
}
//绑定套接字
struct sockaddr_in sockAddr;//其结构根据地址创建socket的协议族不同而不同,struct sockaddr_in/sockaddr_in/SOCKADDR_IN
sockAddr.sin_family = AF_INET;//协议族(地址族)
sockAddr.sin_port = htons(8888);//本地端口端口,将主机数值搞成网络字节序
sockAddr.sin_addr.s_addr = inet_addr("192.168.0.103");//inet_addr将字符串形式的IP地址装换成网络字节顺序的整型值,127.0.0.1为本地回环地址
//中 s_addr宏定义为S_un.S_addr
//sockAddr.sin_addr.S_un.S_addr= inet_addr("127.0.0.1");
int ret = bind(serverSocket, (struct sockaddr*)&sockAddr, sizeof(SOCKADDR_IN));//LPSOCKADDR等同于struct sockaddr*、SOCKADDR*
//int bind(int sockfd,const struct socketaddr * addr,
if (ret == SOCKET_ERROR)//
{
std::cout << "绑定失败!" << std::endl;
WSACleanup();//释放套接字资源
closesocket(serverSocket);//关闭套接字
return -1;
}
//进入监听状态
listen(serverSocket, 5);//5代表可以排队的最大连接数
if (listen(serverSocket, 5) < 0)//listen返回0表示监听中,返回-1表示监听失败
{
std::cout << "监听失败!" << std::endl;
WSACleanup();//释放套接字资源
closesocket(serverSocket);//关闭套接字
return -1;
}
//接收客户端请求
//SOCKADDR clsocket;
SOCKADDR clsocket;
int len = sizeof(SOCKADDR);
SOCKET newSock = accept(serverSocket, (SOCKADDR*)&clsocket, &len);
if (newSock == SOCKET_ERROR)
{
std::cout << "服务器——客户端连接失败!" << std::endl;
return -1;
}
std::cout << "服务器——客户端连接完成" << std::endl;
//连接成功,开启多线程收发
HANDLE send_thread, recv_thread;//定义发送和接收线程,句柄方式定义
DWORD nSthread, nRthread; //定义返回的线程ID号
/*使用CreateThread()创建线程
第一个参数表示内核对象安全属性
第二个参数表示线程栈空间大小,0表示默认1MB
第三个参数表示执行的线程函数地址
第四个参数表示传递给线程函数的参考,本线程传递的是对端的套接字
第五个参数表示额外的标志,用来控制线程的创建,0表示创建完之后线程立即执行,CREAT——SUSPENDED表示创建后暂停执行,知道调用ResumeThread()函数
第六个参数表示返回的线程号
*/
send_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)sendMessage, (LPVOID)newSock, 0, &nSthread);//发送线程
recv_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)recvMessage, (LPVOID)newSock, 0, &nRthread);//接收线程
/*希望线程完成后才继续执行主进程,直到bExit事件被激活(由SetEvent()触发事件的函数激活)
主线程才继续执行
*/
WaitForSingleObject(bExit, INFINITE);
closesocket(newSock);
closesocket(serverSocket);
WSACleanup();
return 0;
}
客户端代码:
#include
#include
#include
#pragma comment (lib, "ws2_32.lib")
#pragma warning(disable : 4996)
using namespace std;
/*全双工通信*/
//---------------通信用-----------------------
const int bufLen = 1024;
char reciveBuf[bufLen];//字符数组
char sendBuf[bufLen];//存放发送数据
//---------------通信用-----------------------
WSAEVENT bExit = WSACreateEvent();//设置一个多线程停止事件
//接收线程函数
DWORD WINAPI recvMessage(LPVOID parsock)
{
SOCKET recvsock = (SOCKET)parsock; //对端客户机传来的套接字
int err; //错误代码
while (true)
{
memset(reciveBuf, 0, sizeof(reciveBuf));//初始化接收缓存
if (recv(recvsock, reciveBuf, bufLen, 0) != SOCKET_ERROR)
{//接收成功
cout << "收到内容:" << reciveBuf << endl;
if (strcmp(reciveBuf, "bye") == 0)
{//客户端结束通信,输入bye
SetEvent(bExit);//触发事件,结束线程,继续执行主进程
break;
}
}
else
{//出错了
err = WSAGetLastError();
if (err == WSAECONNRESET)
{//连接有问题了
cout << "The peer is closed" << endl;
continue;//继续下一次
}
SetEvent(bExit);//f否则就是事件触发直接退出
break;
}
}
closesocket(recvsock);//关闭套接字
WSACleanup();//释放资源
return 0;
}
//发送线程函数
DWORD WINAPI sendMessage(LPVOID parsock)
{
SOCKET sendsock = (SOCKET)parsock; //对端客户机传来的套接字
while (true)
{
cout << "请输入要发送的信息:" << endl;
cin.getline(sendBuf, sizeof(sendBuf));
send(sendsock, sendBuf, sizeof(sendBuf), 0);//发送消息
if (strcmp(sendBuf, "bye") == 0)
{
SetEvent(bExit);//事件触发
break;
}
}
closesocket(sendsock);
WSACleanup();//释放资源
return 0;
}
int main()
{
//初始化套接字动态库
WSADATA data;
//WSAStartup(MAKEWORD(2, 0), &data);
if (WSAStartup(MAKEWORD(2, 0), &data) != 0)//为0则初始化成功
{
std::cout << "初始化动态套接字失败!" << std::endl;
return -1;
}
SOCKET client = socket(AF_INET, SOCK_STREAM, 0);//创建套接字
if (client == INVALID_SOCKET)
{
std::cout << "套接字创建失败!" << std::endl;
WSACleanup();//释放套接字资源
return -1;
}
//配置目的地址信息,ip地址,端口,协议类型
sockaddr_in addr;
addr.sin_addr.S_un.S_addr = inet_addr("192.168.0.103");
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
std::cout << "连接中......" << std::endl;
int ret = connect(client, (SOCKADDR*)&addr, sizeof(addr));
if (ret == -1)//返回0表示连接成功,-1连接失败
{
std::cout << "连接失败!" << std::endl;
return -1;
}
std::cout << "连接成功!" << std::endl;
//连接成功,开启多线程收发
HANDLE send_thread, recv_thread;//定义发送和接收线程,句柄方式定义
DWORD nSthread, nRthread; //定义返回的线程ID号
/*使用CreateThread()创建线程
第一个参数表示内核对象安全属性
第二个参数表示线程栈空间大小,0表示默认1MB
第三个参数表示执行的线程函数地址
第四个参数表示传递给线程函数的参数,本线程传递的是对端的套接字
第五个参数表示额外的标志,用来控制线程的创建,0表示创建完之后线程立即执行,CREAT——SUSPENDED表示创建后暂停执行,知道调用ResumeThread()函数
第六个参数表示返回的线程号
*/
send_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)sendMessage, (LPVOID)client, 0, &nSthread);//发送线程
recv_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)recvMessage, (LPVOID)client, 0, &nRthread);//接收线程
/*希望线程完成后才继续执行主进程,直到bExit事件被激活(由SetEvent()触发事件的函数激活)
主线程才继续执行
*/
WaitForSingleObject(bExit, INFINITE);
closesocket(client);
WSACleanup();
return 0;
}