最近项目中需要用到设备通信,特在VS2013中利用Windows自带的API进行服务端和客户端通信测试。
由于实际应用需要服务器和客户端不间断实时全双工通信,特利用多线程的方式简单实现socket通信。
socket的通信机制,我不做过多的阐述,相关的参考文档较多,接下来从code的角度实现多线程通信。
服务端程序如下:
-------------------------------
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include "winsock2.h"
#pragma comment(lib,"ws2_32.lib")
char recvBuf[100], sendBuf[100];
SOCKET sockConn;
DWORD WINAPI sendFun(LPVOID IpParameter);
DWORD WINAPI receiveFun(LPVOID lpParamter);
/*
进行全双工通信,采用多线程的方式!
时间:2018/07/17
*/
int main()
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)//调用函数,指明使用的socket版本!wsaData返回版本信息
{
std::cout << "初始化失败!" << std::endl;
std::cin.get();
return 0;
}
//创建用于监听的套接字,即服务器的套接字
/*
struct sockaddr_in
{
sa_family_t sin_family;//地址族
uint16_t sin_port;//16位TCP/UDP端口号
struct in_addr sin_addr;//32位IP地址
char sin_zero[8];//不使用
}
struct in_addr
{
In_addr_t s_addr;//32位IPv4地址
}
------------------------------------------------------------------
htons()将端口号由主机字节序转换为网络字节序的整数值
inet_addr()将一个IP字符串转化为一个网络字节序的整数值
inte_ntoa()将一个sin_addr结构体输入为IP字符串
htonl()作用与htons()一样,不过针对32位
------------------------------------------------------------------
*/
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);//套接字
if (sockSrv == -1)
{
std::cout << "初始化socket失败!" << std::endl;
std::cin.get();
return 0;
}
SOCKADDR_IN addrSrv;//建立Internet环境下套接字地址形式
addrSrv.sin_family = AF_INET;//地址族形式
addrSrv.sin_port = htons(8080);//1024以上的端口号
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//IPv4地址(不确定地址,也表示所有地址)
int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));//将套接字进行地址和端口的绑定
if (retVal == SOCKET_ERROR)
{
std::cout << "绑定失败! " << WSAGetLastError() << std::endl;
std::cin.get();
return 0;
}
/*
listen()将主动连接套接字口变成被动连接套接字,使得一个进程可以接受其它进程的请求,从而变成一个服务器进程
listen()函数一般在bind()函数后,accept()函数之前
int listen(int sockfd,int backlog);
backlog:等待连接队列的最大长度(某一时刻同时允许最多有backlog个客户端和服务端进行连接)
*/
if (listen(sockSrv, 10) == SOCKET_ERROR)
{
std::cout << "监听失败! " << WSAGetLastError() << std::endl;
std::cin.get();
return 0;
}
/*
accept(sockfd,sockaddr *addr,socklen_t *len);
accept默认为阻塞进程,直到有一个客户连接建立后返回,其返回一个新可用的套接字(连接套接字)
在调用listen函数后,一个套接字会从主动连接的套接字变身为一个监听套接字
sockfd为监听套接字,accept返回一个连接套接字,表示一个网络已经存在点点连接
----------------------------------------------------------------------------
send(socket s,const char *buf,int len,int flags);
无论是客户端还是服务端都用send函数来向TCP连接的另一端发送数据,recv()函数也是一样的
buf为存放应用程序发送数据的缓冲区
*/
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while (1)
{
//等待客户请求到来
sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);//sockConn为连接套接字
if (sockConn == SOCKET_ERROR)
{
std::cout << "等待请求失败!" << WSAGetLastError() << std::endl;
break;
}
//发送数据
char sendbuf[] = "通信已建立!";
int iSend = send(sockConn, sendbuf, sizeof(sendbuf), 0);
if (iSend == SOCKET_ERROR)
{
std::cout << "发送失败!" << std::endl;
break;
}
recv(sockConn, recvBuf, sizeof(recvBuf), 0);//接收建立起连接的数据
std::cout << recvBuf << " 客户端的IP是:" << inet_ntoa(addrClient.sin_addr) << std::endl;
HANDLE hSendThread = CreateThread(NULL, 0, sendFun, NULL, 0, NULL);//在线程中进行数据的发送
HANDLE hReceiveThread = CreateThread(NULL, 0, receiveFun, NULL, 0, NULL);//在线程中进行数据的接收
CloseHandle(hSendThread);
CloseHandle(hReceiveThread);
}
closesocket(sockSrv);
WSACleanup();//调用此函数解除与socket库的绑定,并释放socket库所占用的系统资源
system("pause");
return 0;
}
DWORD WINAPI sendFun(LPVOID IpParameter)
{
while (true)
{
memset(sendBuf, NULL, sizeof(sendBuf));
std::cin >> sendBuf;
std::cin.get();
send(sockConn, sendBuf, sizeof(sendBuf), 0);
}
closesocket(sockConn);
return 0;
}
DWORD WINAPI receiveFun(LPVOID IpParameter)
{
while (true)
{
memset(recvBuf, NULL, sizeof(recvBuf));
//接收数据
int flag = (int)recv(sockConn, recvBuf, sizeof(recvBuf), 0);
if (flag>0 )//有数据才响应
{
std::cout << "接收到的数据为:" << recvBuf << std::endl;
}
else
{
}
}
closesocket(sockConn);
return 0;
}
-------------------------------
客户端程序如下:
------------------------------
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include "winsock2.h"
#pragma comment(lib,"ws2_32.lib")
SOCKET sockClient;
char buffs[100], buff[100];
DWORD WINAPI sendFun(LPVOID lpParameter);
DWORD WINAPI receiveFun(LPVOID lpParameter);
int main()
{
WSADATA wsaData;
char buff[1024];
memset(buff, 0, sizeof(buff));
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
std::cout << "初始化winsock失败!" << std::endl;
std::cin.get();
return 0;
}
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(8080);//端口号
addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.74.119");//IP地址
//创建套接字
sockClient = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKET_ERROR == sockClient)
{
std::cout << "Socket() error: " << WSAGetLastError() << std::endl;
std::cin.get();
return 0;
}
//向服务器发出连接请求
if (connect(sockClient, (struct sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET)
{
std::cout << "连接失败!" << WSAGetLastError() << std::endl;
std::cin.get();
return 0;
}
else
{
//接收数据
recv(sockClient, buff, sizeof(buff), 0);
std::cout << buff << " 服务端的IP是:" << inet_ntoa(addrSrv.sin_addr) << std::endl;
}
//发送数据
char sendbuffs[] = "通信已建立!";
send(sockClient, sendbuffs, sizeof(buffs), 0);
//在线程中进行数据的收发
while (true)
{
HANDLE hSendThread = CreateThread(NULL, 0, sendFun, NULL, 0, NULL);//在线程中进行数据的发送
HANDLE hReceiveThead = CreateThread(NULL, 0, receiveFun, NULL,0, NULL);//在线程中进行数据的接收
CloseHandle(hSendThread);
CloseHandle(hReceiveThead);
}
//关闭套接字
closesocket(sockClient);
WSACleanup();//释放初始化Ws2_32.dll所分配的资源
system("pause");
std::cin.get();
return 0;
}
DWORD WINAPI sendFun(LPVOID lpParameter)
{
while (true)
{
memset(buffs, NULL, sizeof(buffs));
std::cin >> buffs;
send(sockClient, buffs, sizeof(buffs), 0);
}
closesocket(sockClient);
return 0;
}
DWORD WINAPI receiveFun(LPVOID lpParameter)
{
while (true)
{
memset(buff, NULL, sizeof(buff));
int flag = (int)recv(sockClient, buff, sizeof(buff), 0);
if (flag>0)
{
std::cout << "接收的数据为:" << buff << std::endl;
}
else
{
}
}
closesocket(sockClient);
return 0;
}
------------------------------
程序测试效果:
特别地,我是在一个电脑上进行的不同程序间通信,所以服务端和客户端IP地址一致,若要在不同电脑上运行,可设置IP地址,但是要保持端口号一致。
另外,在关闭界面时,最好先关闭客户端的界面,否则可能会有卡顿的现象。。。