博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C/S模型之TCP群聊
阅读量:4358 次
发布时间:2019-06-07

本文共 7068 字,大约阅读时间需要 23 分钟。

说明:

利用TCP协议和多线程实现群聊功能。一个服务器,多个客户端(同一个程序多次启动)。客户端向服务端发送数据,由服务端进行转发到其他
客户端。

/服务端// WSASever.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include 
#include
#include
#pragma comment (lib,"wSock32.lib")SOCKET sockLink;SOCKET g_psockSockLink[1024] = {
0}; //存放客户端的sockint g_nSocketNum=0; //记录客户端sock的数目//多线程进行接受和转发DWORD WINAPI SeverThread(LPVOID lParam){ int nErr = 0; char pSeverBuff[MAXBYTE] = { 0 }; //接受客户端的数据 char pSendBuff[MAXBYTE] = { 0 }; //显示在窗口,包括来自哪个IP地址,端口号,数据 SOCKET sockLink = (SOCKET)lParam; //当前的客户端sock SOCKADDR_IN sockAddr; int len = sizeof(SOCKADDR_IN); while (TRUE) { //接受客户端 nErr = recv(sockLink,pSeverBuff, MAXBYTE, 0); if (nErr == SOCKET_ERROR) { break; return -1; } //根据sock获取sock地址 getpeername(sockLink, (sockaddr*)&sockAddr, &len); //将Ip、端口号、数据存入pSendBuff sprintf_s(pSendBuff,"%s(%d):%s\n", inet_ntoa(sockAddr.sin_addr), ntohs(sockAddr.sin_port), pSeverBuff); //显示在窗口 printf("%s\n", pSendBuff); //转发 for (int i = 0;i
bind-》listen->accept->recv->send->closesocket SOCKET severSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (severSocket == INVALID_SOCKET) { printf("new socket error!"); } //设置端口号和IP地址、协议。 SOCKADDR_IN sockAddr; sockAddr.sin_port = htons(10086); sockAddr.sin_family = AF_INET; sockAddr.sin_addr.s_addr = htonl (INADDR_ANY); //IP地址表示方法 /*方法1:m_addr.sin_addr.S_un.S_un_b.s_b1 = 192; m_addr.sin_addr.S_un.S_un_b.s_b2 = 168; m_addr.sin_addr.S_un.S_un_b.s_b3 = 0; m_addr.sin_addr.S_un.S_un_b.s_b4 = 1; 方法2: m_addr.sin_addr.S_un.S_un_w.s_w1 = (168 << 8) | 192; m_addr.sin_addr.S_un.S_un_w.s_w2 = (1 << 8) | 0; 方法3: m_addr.sin_addr.S_un.S_addr = (1 << 24) | (0 << 16) | (168 << 8) | 192 方法4; service.sin_addr.s_addr = inet_addr("127.0.0.1"); */ /*sockAddr.sin_addr.S_un.S_un_b.s_b1 = 127; sockAddr.sin_addr.S_un.S_un_b.s_b2 = 0; sockAddr.sin_addr.S_un.S_un_b.s_b3 = 0; sockAddr.sin_addr.S_un.S_un_b.s_b4 = 1;*/ //绑定 if (bind(severSocket, (sockaddr*)&sockAddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) { printf("bind error! %d\n", WSAGetLastError()); } //监听5个 if (listen(severSocket, 5) == SOCKET_ERROR) { printf("listen error!%d\n", WSAGetLastError()); } //创建一个一客户端连接的socket while (true) { //接受来自客户端的sock,并存入客户端的数组中 SOCKET sockLink = accept(severSocket, NULL, NULL); if (sockLink != INVALID_SOCKET) { printf("communication sucess!\n"); } g_psockSockLink[g_nSocketNum++] = sockLink; //每启动一个客户端,启动一条线程 HANDLE hThread = CreateThread(NULL, 0, SeverThread, (LPVOID)sockLink, 0, NULL); //CloseHandle(hThread); if (hThread == NULL) continue; } closesocket(severSocket); closesocket(sockLink); WSACleanup(); return 0;}

 

 

//客户端// WASClient.cpp : 定义控制台应用程序的入口点。////#include 
一定要在#include
前面#include "stdafx.h"#include
#include
#pragma comment (lib,"wSock32.lib")//用于来自接受服务器的数据,避免的send中造成阻塞。DWORD WINAPI RectThread(LPVOID lParam){ SOCKET sockLink = (SOCKET)lParam; char pReturnValue[MAXBYTE] = { 0 }; while (true) { recv(sockLink, pReturnValue, MAXBYTE, 0); printf("%s\n", pReturnValue); } return 0;}int _tmain(int argc, _TCHAR* argv[]){ WORD wVersionRequested; WSADATA wsaData; int err; //版本检测 wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { printf("WSAStartup failed with error: %d\n", err); return 1; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { printf("Could not find a usable version of Winsock.dll\n"); WSACleanup(); return 1; } else printf("The Winsock 2.2 dll was found okay\n"); //程序开始 //创建socket-》连接connect-》发送send-》接受recv-》释放closesocke SOCKET clientSocket=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (clientSocket == INVALID_SOCKET) { printf("new socket error!"); } SOCKADDR_IN sockAddr; //一定要把主机字节序换成网络字节序 并是short类型 htons() sockAddr.sin_port = htons(10086); sockAddr.sin_family = AF_INET; sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //IP地址表示方法 /*方法1: m_addr.sin_addr.S_un.S_un_b.s_b1 = 192; m_addr.sin_addr.S_un.S_un_b.s_b2 = 168; m_addr.sin_addr.S_un.S_un_b.s_b3 = 0; m_addr.sin_addr.S_un.S_un_b.s_b4 = 1; 方法2: m_addr.sin_addr.S_un.S_un_w.s_w1 = (168 << 8) | 192; m_addr.sin_addr.S_un.S_un_w.s_w2 = (1 << 8) | 0; 方法3: m_addr.sin_addr.S_un.S_addr = (1 << 24) | (0 << 16) | (168 << 8) | 192*/ /*sockAddr.sin_addr.S_un.S_un_b.s_b1 = 127; sockAddr.sin_addr.S_un.S_un_b.s_b2 = 0; sockAddr.sin_addr.S_un.S_un_b.s_b3 = 0; sockAddr.sin_addr.S_un.S_un_b.s_b4 = 1; */ //连接 if (connect(clientSocket, (sockaddr*)&sockAddr, sizeof(SOCKADDR_IN)) != SOCKET_ERROR) { printf("communication sucess!\n"); } char pClientBuf[MAXBYTE] = { 0 }; //存放输入数据 //启动线程 HANDLE hThread = CreateThread(NULL, 0, RectThread, (LPVOID)clientSocket, 0, NULL); if (hThread == NULL) { printf("CreateThread Error num:%d", GetLastError()); CloseHandle(hThread); } CloseHandle(hThread); //请求连接,发送数据 while (TRUE) { gets_s(pClientBuf); int nSendErr=send(clientSocket, pClientBuf, MAXBYTE, 0); if (nSendErr== SOCKET_ERROR) { break; } } WSACleanup(); closesocket(clientSocket); return 0;}

 

 

注意点:

1.#include <WinSock2.h>一定要在#include <Windows.h>前面
如:
#include <WinSock2.h>
#include <Windows.h>

2.设定端口号时,一定要把主机字节序换成网络字节序 并是short类型 htons()

sockAddr.sin_port = htons(10086);

3.网络连接的流程:

服务端:创建socket->绑定bind->监听listen->接受客户端的套接字accept->接收recv->发送send->释放closesocket
客户端://创建socket-》连接connect-》发送send-》接受recv-》释放closesocke

4.getpeername(sockLink, (sockaddr*)&sockAddr, &len);

该函数可以根据当前的sock获取对象的sock地址,从而获取对应的IP地址、端口号,协议。

5.IP地址表示方法

方法1: m_addr.sin_addr.S_un.S_un_b.s_b1 = 192;
m_addr.sin_addr.S_un.S_un_b.s_b2 = 168;
m_addr.sin_addr.S_un.S_un_b.s_b3 = 0;
m_addr.sin_addr.S_un.S_un_b.s_b4 = 1;
方法2; service.sin_addr.s_addr = inet_addr("192.168.0.1");
方法3: m_addr.sin_addr.S_un.S_un_w.s_w1 = (168 << 8) | 192; m_addr.sin_addr.S_un.S_un_w.s_w2 = (1 << 8) | 0;
方法4: m_addr.sin_addr.S_un.S_addr = (1 << 24) | (0 << 16) | (168 << 8) | 192

6.SOCKET sockLink = accept(severSocket, NULL, NULL);

accept返回的是一个新的sock,该sock可以与客户端进行连接。就好比服务端与客户端建立一条管道,两者间随时可以进行通信。sockLink与clientSocket
是一对组合。因此不同的客户端启动,将会有不同的sock接入服务端。

7.

问题:客户端为什么专门启动一条线程来接受消息?
解析:首先,该程序是群聊功能,无法确定别人的客户端什么时候回发送消息过来。
其次,如何将send和recv写在同一个while中,当send发送消息后,如果别人客户端没有消息进来,此时就在recv阻塞,直到其他客户端发来消息才会解除,
该客户端才可以继续发送消息,无法实现一个客户端发送多次消息。

 

转载于:https://www.cnblogs.com/gd-luojialin/p/7694172.html

你可能感兴趣的文章
Xmind?
查看>>
spring+quartz 实现定时任务三
查看>>
day2-三级菜单
查看>>
linux下升级4.5.1版本gcc
查看>>
Beanutils
查看>>
FastJson
查看>>
excel4j
查看>>
Thread
查看>>
char * 与char []探究理解
查看>>
QT窗体显示在屏幕中间位置
查看>>
emmet使用技巧
查看>>
RPC-Thrift(二)
查看>>
MSSQL for Linux 安装指南
查看>>
【Golang 接口自动化08】使用标准库httptest完成HTTP请求的Mock测试
查看>>
前端必读:浏览器内部工作原理
查看>>
Uri、URL和URN三者的区别
查看>>
数据字典的转换
查看>>
二维数组按照指定的字段排序的函数
查看>>
linux安装Mac的默认Monaco字体
查看>>
java语言的特点
查看>>