socket通信
首先要了解socket的通信流程。先来看TCP方式通信的流程图。

接着来看,UDP的通信流程,udp通信直接发送,不会管对方所处的状态是什么。接收也是一直接收。
这些都只是流程,可能看到这里,大佬们都觉得这都看过很多遍的东西了,赶紧上代码。下面把一个简单的实例放一下,是在vs2019上写的,vs2019虽然普遍是c++环境,生成的文件也是.cpp结尾的,但是还是可以改为.c结尾的C语言文件。
TCP的客户端
#pragma comment(lib, "WS2_32")
#include <iostream>
#include <WinSock2.h>
#include <stdio.h>
#include <assert.h>
#define MAX_PACKET_SIZE 10240 // 数据包的最大长度,单位是sizeof(char)
#define MAXFILEDIRLENGTH 256 // 存放文件路径的最大长度
#define PORT 4096 // 端口号
//#define SERVER_IP "192.168.0.101" // server端的IP地址
#define SERVER_IP "127.0.0.1" // server端的IP地址
SOCKET g_sClient;
char g_szBuff[MAX_PACKET_SIZE + 1];
// 初始化socket库
bool InitSocket()
{
printf("InitSocket() invoke begin\n");
// 初始化socket dll
WSADATA wsaData;
WORD socketVersion = MAKEWORD(2, 2);
if (::WSAStartup(socketVersion, &wsaData) != 0)
{
printf("Init socket dll error\n");
exit(-1);
}
printf("InitSocket() invoke end return true \n");
return true;
}
// 与server端连接进行传输
bool ConectToServer()
{
// 初始化socket套接字
if (SOCKET_ERROR == (g_sClient = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
{
printf("Init Socket Error!\n");
exit(-1);
}
sockaddr_in servAddr;
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(PORT);
servAddr.sin_addr.S_un.S_addr = ::inet_addr(SERVER_IP);
if (INVALID_SOCKET == (::connect(g_sClient, (sockaddr*)&servAddr, sizeof(sockaddr_in))))
{
printf("Connect to Server Error!\n");
exit(-1);
}
// 输入数据传输到server端
while (true == SendDataToServer())
{
}
// 接收server端传过来的信息,直到保存文件成功为止
while (true == ProcessMsg())
{
}
return true;
}
// 关闭socket库
bool CloseSocket()
{
// 关闭套接字
::closesocket(g_sClient);
// 释放winsock库
::WSACleanup();
return true;
}
// 把用户输入的数据传送到server端
bool SendDataToServer()
{
char Data[MAXFILEDIRLENGTH];
printf("Input the Data: \n");
fgets(Data, MAXFILEDIRLENGTH, stdin);
for (int i = 0; i < MAXFILEDIRLENGTH; i++)
{
Data[i] = Data[i] - 48;
}
if (SOCKET_ERROR == ::send(g_sClient, (char*)(&Data), 256, 0))
{
printf("Send Data Error!\n");
exit(-1);
}
return true;
}
// 处理server端传送过来的消息
bool ProcessMsg()
{
int nRecv = ::recv(g_sClient, g_szBuff, MAX_PACKET_SIZE + 1, 0);
for (int i = 0; i < 64; i++)
{
printf("%d ", g_szBuff[i]);
}
printf("\n");
return true;
}
int main()
{
InitSocket();
while (1)
{
ConectToServer();
}
CloseSocket();
return 0;
}
TCP的服务器端
#pragma comment(lib, "WS2_32")
#include <iostream>
#include <WinSock2.h>
#include <stdio.h>
#include <assert.h>
#define PORT 4096 // 端口号
#define MAX_PACKET_SIZE 10240 // 数据包的最大长度,单位是sizeof(char)
char g_szBuff[MAX_PACKET_SIZE + 1];
// 初始化socket库
bool InitSocket();
// 监听Client的消息
void ListenToClient();
// 解析消息进行相应的处理
bool ProcessMsg(SOCKET sClient);
// 关闭socket库
bool CloseSocket();
int main()
{
InitSocket();
while (1) {
ListenToClient();
}
CloseSocket();
return 0;
}
bool InitSocket()
{
// 初始化socket dll
WSADATA wsaData;
WORD socketVersion = MAKEWORD(2, 2);
if (::WSAStartup(socketVersion, &wsaData) != 0)
{
printf("Init socket dll error\n");
return false;
}
return true;
}
void ListenToClient()
{
// 创建socket套接字
SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (SOCKET_ERROR == sListen)
{
printf("Init Socket Error!\n");
return;
}
// 绑定socket到一个本地地址
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (::bind(sListen, (LPSOCKADDR)&sin, sizeof(sockaddr_in)) == SOCKET_ERROR)
{
printf("Bind Error!\n");
return;
}
// 设置socket进入监听状态
if (::listen(sListen, 10) == SOCKET_ERROR)
{
printf("Listen Error!\n");
return;
}
printf("Listening To Client...\n");
// 循环接收client端的连接请求
sockaddr_in ClientAddr;
int nAddrLen = sizeof(sockaddr_in);
SOCKET sClient;
while (INVALID_SOCKET == (sClient = ::accept(sListen, (sockaddr*)&ClientAddr, &nAddrLen)))
{
}
while (true == ProcessMsg(sClient))
{
}
// 关闭同客户端的连接
::closesocket(sClient);
::closesocket(sListen);
}
bool ProcessMsg(SOCKET sClient)
{
int nRecv = ::recv(sClient, g_szBuff, MAX_PACKET_SIZE + 1, 0);
printf("Receive Data :\n");
for (int i = 0; i < 64; i++)
{
printf("%x ",g_szBuff[i]);
}
return true;
}
bool CloseSocket()
{
// 释放winsock库
::WSACleanup();
return true;
}
// 把用户输入的数据传送到client端
bool SendDataToClient()
{
SOCKET g_sClient;
char Data[64];
printf("Input the Data: \n");
fgets(Data, 64, stdin);
for (int i = 0; i < 64; i++)
{
Data[i] = Data[i] - 48;
}
if (SOCKET_ERROR == ::send(g_sClient, (char*)(&Data), 256, 0))
{
printf("Send Data Error!\n");
exit(-1);
}
return true;
}
UDP客户端
#include <iostream>
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll
#pragma warning(disable : 4996)
#define BUF_SIZE 100
int main() {
//初始化DLL
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//创建套接字
SOCKET sock = socket(PF_INET, SOCK_DGRAM, 0);
//服务器地址信息
sockaddr_in servAddr;
memset(&servAddr, 0, sizeof(servAddr)); //每个字节都用0填充
servAddr.sin_family = PF_INET;
// servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servAddr.sin_addr.s_addr = inet_addr("196.168.0.12");
servAddr.sin_port = htons(8000);
//不断获取用户输入并发送给服务器,然后接受服务器数据
sockaddr fromAddr;
int addrLen = sizeof(fromAddr);
while (1)
{
char buff[BUF_SIZE] = { 0 };
printf("Input a string: ");
gets_s(buff);
sendto(sock, buff, strlen(buff), 0, (struct sockaddr*)&servAddr, sizeof(servAddr));
}
closesocket(sock);
WSACleanup();
return 0;
}
UDP服务器端
#include <iostream>
#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll
#pragma warning(disable : 4996)
#define BUF_SIZE 100
int main() {
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//创建套接字,SOCK_DGRAM指明使用 UDP 协议
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
//绑定套接字
sockaddr_in servAddr;
memset(&servAddr, 0, sizeof(servAddr)); //每个字节都用0填充
servAddr.sin_family = PF_INET; //使用IPv4地址
servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取IP地址
servAddr.sin_port = htons(8090); //端口
bind(sock, (SOCKADDR*)&servAddr, sizeof(SOCKADDR));
//接收客户端请求
SOCKADDR cliAddr; //客户端地址信息
int nSize = sizeof(SOCKADDR);
char buff[BUF_SIZE]; //缓冲区
while (1) {
int strLen = recvfrom(sock, buff, BUF_SIZE, 0, &cliAddr, &nSize);
//sendto(sock, buff, strLen, 0, &cliAddr, nSize);
printf("\n receive data: \n");
for (int i = 0; i < strLen; i++)
{
printf("%d ",buff[i]);
}
}
closesocket(sock);
WSACleanup();
return 0;
}
MFC界面
在vs2019上,添加MFC的工程,这些介绍的有很多,这里就不赘述了。
有一个简单的例程,简单计算器,可以参考。
简单计算器
多线程
在vs2019上,想实现一个button,点击创建一个线程,保持接收数据。但是,在实现的时候,总是弹出错误,
error C2440: “类型转换”:无法从“overloaded-function”转换为“LPTHREAD_START_ROUTINE”
非静态成员引用必须与特定对象相对
总结一下方法:
第一种方法:改为静态成员变量
带来的问题:静态成员函数只能直接访问静态成员变量和静态成员函数。静态变量与静态函数多了无疑会加重内存的负担,并且使用起来比较复杂。
第二种方法:借助于线程函数参数的传入,由此一来便可尽可能的减少静态变量、静态函数的使用。
第二种方法可以方便使用,此处是创建线程的方法和使用。
//线程函数
static UINT WINAPI ReceiveData(LPVOID param)
{
CMFCUDPDlg* obj = (CMFCUDPDlg*)param;
// call the member
obj->DebugList6500.InsertString(0, _T("INFO::测试List \n"));function
// to do the work in our new thread
}
void CMFCUDPDlg::OnClickedListendebug6500()
{
// TODO: 在此添加控件通知处理程序代码
HANDLE hThread;
DWORD dwThreadId;
hThread = CreateThread(NULL // 默认安全属性
, NULL // 默认堆栈大小
, (LPTHREAD_START_ROUTINE)(ReceiveData) // 线程入口地址
, (LPVOID)this //传递给线程函数的参数
, 0 // 指定线程立即运行
, &dwThreadId //线程ID号
);
CloseHandle(hThread);//关闭线程句柄,内核引用计数减一
}
使用,第二种方法,将“this”参数指针作为参数,传给线程函数,然后就可以在线程函数中,调用类中的成员变量。我使用的是MFC创建的List控件,然后就可以在线程函数中调用,并添加打印信息。
版权声明:本文为qq_40839071原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。