Socket之网络聊天室-client
使用socket实现简单网络聊天室之客户端
基本流程
1. 初始化 WinSock: 调用 WSAStartup
初始化 WinSock 库。
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
return -1;
}
2. 创建套接字: 调用 socket
创建一个新的套接字。
SOCKET hSock;
hSock = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN servAdr;
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_port = htons(9999);
inet_pton(AF_INET, "139.155.130.173", &servAdr.sin_addr);
3. 连接服务器: 使用服务器的 IP 地址和端口号,通过 connect
连接到服务器。
if (connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr))== SOCKET_ERROR) {
std::cout << "connect error : " << GetLastError();
return -1;
}
else {
std::cout << "welcome chat room,please enter your name: ";
}
4. 发送和接收线程:
创建两个线程,一个用于发送消息 (SendMsg
),另一个用于接收消息 (RecvMsg
)。
发送线程从控制台读取用户输入,并将其发送到服务器。
接收线程不断从服务器接收消息,并显示在控制台上。
//循环发消息
HANDLE hSendHand = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SendMsg, (void*)&hSock, 0,NULL);
//循环收消息
HANDLE hRecvHand = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RecvMsg, (void*)&hSock, 0, NULL);
5. 线程同步: 使用 WaitForSingleObject
等待两个线程结束。
WaitForSingleObject(hSendHand, INFINITE);
WaitForSingleObject(hRecvHand, INFINITE);
完整代码实现:
#include<iostream>
#include<WinSock2.h>
#include<WS2tcpip.h>
#include<windows.h>
// link with Ws2_32.lib
#pragma comment(lib,"Ws2_32.lib")
#define BUF_SIZE 1024
char szMsg[BUF_SIZE];
unsigned SendMsg(void *arg) {
SOCKET sock = *((SOCKET*)arg);
while (true)
{
//scanf_s("%s", szMsg);
std::cin >> szMsg;
if (!strcmp(szMsg, "QUIT\n") || !strcmp(szMsg, "quit\n")) {
closesocket(sock);
exit(0);
}
send(sock, szMsg, strlen(szMsg), 0);
}
return 0;
}
unsigned RecvMsg(void* arg) {
SOCKET sock = *((SOCKET*)arg);
char msg[BUFSIZ];
while (true)
{
int len = recv(sock, msg, sizeof(msg) - 1, 0);
if (len == -1) {
return -1;
}
msg[len] = '\0';
std::cout << msg;
}
return 0;
}
int main() {
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
return -1;
}
if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2) {
WSACleanup();
return -1;
}
//创建
SOCKET hSock;
hSock = socket(AF_INET, SOCK_STREAM, 0);
//绑定
SOCKADDR_IN servAdr;
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_port = htons(9999);
inet_pton(AF_INET, "139.155.130.173", &servAdr.sin_addr);
//连接服务器
if (connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr))== SOCKET_ERROR) {
std::cout << "connect error : " << GetLastError();
return -1;
}
else {
std::cout << "welcome chat room,please enter your name: ";
}
//循环发消息
HANDLE hSendHand = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SendMsg, (void*)&hSock, 0,NULL);
//循环收消息
HANDLE hRecvHand = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RecvMsg, (void*)&hSock, 0, NULL);
//等待线程结束
WaitForSingleObject(hSendHand, INFINITE);
WaitForSingleObject(hRecvHand, INFINITE);
WSACleanup();
}
存在的问题:
退出机制: 代码中使用
exit(0)
在发送QUIT
消息后直接退出程序。这样做可能会导致接收线程在退出时没有机会进行清理。更好的方法是设置一个全局的退出标志,在检查到该标志时让两个线程安全退出。缓冲区大小: 接收缓冲区
msg
使用的是BUFSIZ
,它可能小于发送缓冲区szMsg
的BUF_SIZE
。为保持一致性和避免潜在的溢出问题,这两个缓冲区大小应该相同。输入处理: 使用
std::cin >> szMsg;
只能读取空格前的字符串,这可能无法发送包含空格的完整消息。也可以使用std::getline(std::cin, szMsg);
来读取整行输入。资源泄漏: 在调用
exit(0)
或发生错误时,线程可能没有正确关闭套接字就退出了,导致资源泄漏。线程安全: 在多线程环境中操作套接字和其他共享资源时需要特别小心。虽然这个例子简单且可能不会出现立即的问题,但在更复杂的场景下可能需要考虑额外的线程同步。
硬编码的 IP 和端口: 服务器地址和端口硬编码在代码中,这样做不够灵活。更好的做法是从配置文件或命令行参数中读取这些信息。
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果