C/S模型之TCP群聊
说明:
利用TCP协议和多线程实现群聊功能。一个服务器,多个客户端(同一个程序多次启动)。客户端向服务端发送数据,由服务端进行转发到其他
客户端。
/服务端
// WSASever.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <WinSock2.h>
#include <Windows.h>
#include <vector>
#pragma comment (lib,"wSock32.lib") SOCKET sockLink;
SOCKET g_psockSockLink[] = {}; //存放客户端的sock
int g_nSocketNum=; //记录客户端sock的数目 //多线程进行接受和转发
DWORD WINAPI SeverThread(LPVOID lParam)
{
int nErr = ;
char pSeverBuff[MAXBYTE] = { }; //接受客户端的数据
char pSendBuff[MAXBYTE] = { }; //显示在窗口,包括来自哪个IP地址,端口号,数据 SOCKET sockLink = (SOCKET)lParam; //当前的客户端sock SOCKADDR_IN sockAddr;
int len = sizeof(SOCKADDR_IN); while (TRUE)
{
//接受客户端
nErr = recv(sockLink,pSeverBuff, MAXBYTE, );
if (nErr == SOCKET_ERROR)
{
break;
return -;
} //根据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 = ;i<g_nSocketNum;++i)
{
//不为当前发送方的sock
if (g_psockSockLink[i] != sockLink)
{
send(g_psockSockLink[i], pSendBuff, MAXBYTE, );
}
} }
//当客户端关闭时,服务端也随之关闭
//if (nErr == INVALID_SOCKET)
//return-1;
return ;
} int _tmain(int argc, _TCHAR* argv[])
{
//版本检测
WORD wVersionRequested;
WSADATA wsaData;
int err; wVersionRequested = MAKEWORD(, ); err = WSAStartup(wVersionRequested, &wsaData);
if (err != ) { printf("WSAStartup failed with error: %d\n", err);
return ;
} if (LOBYTE(wsaData.wVersion) != || HIBYTE(wsaData.wVersion) != )
{
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return ;
}
else
printf("The Winsock 2.2 dll was found okay\n"); //程序开始 //创建socket->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();
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, ) == 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, , SeverThread, (LPVOID)sockLink, , NULL);
//CloseHandle(hThread);
if (hThread == NULL)
continue;
} closesocket(severSocket);
closesocket(sockLink);
WSACleanup();
return ; }
//客户端
// WASClient.cpp : 定义控制台应用程序的入口点。
// //#include <WinSock2.h>一定要在#include <Windows.h>前面 #include "stdafx.h"
#include <WinSock2.h>
#include <Windows.h>
#pragma comment (lib,"wSock32.lib") //用于来自接受服务器的数据,避免的send中造成阻塞。
DWORD WINAPI RectThread(LPVOID lParam)
{
SOCKET sockLink = (SOCKET)lParam;
char pReturnValue[MAXBYTE] = { };
while (true)
{
recv(sockLink, pReturnValue, MAXBYTE, );
printf("%s\n", pReturnValue);
}
return ;
} int _tmain(int argc, _TCHAR* argv[])
{
WORD wVersionRequested;
WSADATA wsaData;
int err; //版本检测
wVersionRequested = MAKEWORD(, ); err = WSAStartup(wVersionRequested, &wsaData);
if (err != ) { printf("WSAStartup failed with error: %d\n", err);
return ;
} if (LOBYTE(wsaData.wVersion) != || HIBYTE(wsaData.wVersion) != )
{
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return ;
}
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();
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] = { }; //存放输入数据 //启动线程
HANDLE hThread = CreateThread(NULL, , RectThread, (LPVOID)clientSocket, , 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, );
if (nSendErr== SOCKET_ERROR)
{
break;
}
} WSACleanup();
closesocket(clientSocket); return ;
}
注意点:
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阻塞,直到其他客户端发来消息才会解除,
该客户端才可以继续发送消息,无法实现一个客户端发送多次消息。
C/S模型之TCP群聊的更多相关文章
- Java-->实现群聊功能(C/S模式--TCP协议)
--> Java 对TCP协议的支持: --> java.net包中定义了两个类ServerSocket 和Socket ,分别用来实现双向连接的server 端和client 端. -- ...
- [Python 网络编程] TCP编程/群聊服务端 (二)
群聊服务端 需求分析: 1. 群聊服务端需支持启动和停止(清理资源); 2. 可以接收客户端的连接; 接收客户端发来的数据 3. 可以将每条信息分发到所有客户端 1) 先搭架子: #TCP Serve ...
- 102.tcp实现多线程连接与群聊
协议之间的关系 socket在哪 socket是什么 Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP ...
- Java 网络编程 -- 基于TCP 实现聊天室 群聊 私聊
分析: 聊天室需要多个客户端和一个服务端. 服务端负责转发消息. 客户端可以发送消息.接收消息. 消息分类: 群聊消息:发送除自己外所有人 私聊消息:只发送@的人 系统消息:根据情况分只发送个人和其他 ...
- 一套高可用、易伸缩、高并发的IM群聊架构方案设计实践
本文原题为“一套高可用群聊消息系统实现”,由作者“于雨氏”授权整理和发布,内容有些许改动,作者博客地址:alexstocks.github.io.应作者要求,如需转载,请联系作者获得授权. 一.引言 ...
- websocket学习和群聊实现
WebSocket协议可以实现前后端全双工通信,从而取代浪费资源的长轮询.在此协议的基础上,可以实现前后端数据.多端数据,真正的实时响应.在学习WebSocket的过程中,实现了一个简化版群聊,过程和 ...
- (1)基于tcp协议的编程模型 (2)tcp协议和udp协议的比较 (3)基于udp协议的编程模型 (4)反射机制
1.基于tcp协议的编程模型(重中之重)1.1 编程模型服务器: (1)创建ServerSocket类型的对象,并提供端口号: (2)等待客户端的连接请求,调用accept()方法: (3)使用输入输 ...
- websocket实现群聊和单聊(转)
昨日内容回顾 1.Flask路由 1.endpoint="user" # 反向url地址 2.url_address = url_for("user") 3.m ...
- 网络基础七层模型与TCP/IP协议
1.网络基础 1.1 什么是网络 网络就是计算机网络是一组计算机或网络设备通过有形 的线缆或无形的媒介如无线,连接起来,按照一定的 规则,进行通信的集合. 网络通信就是指终端设备之间通过计算机网络进行 ...
随机推荐
- Linux的账号口令机制及其爆破
账号保存 谈到linux的账号认证,其实就是如何保存于通过口令(password)鉴别,这里首先要将两个文件,一个是/etc/passwd,另外一个是/etc/shadow. /etc/passwd文 ...
- 【CF896E】Welcome home, Chtholly 暴力+分块+链表
[CF896E]Welcome home, Chtholly 题意:一个长度为n的序列ai,让你支持两种操作: 1.l r x:将[l,r]中ai>x的ai都减去x.2.l r x:询问[l,r ...
- mysql补充(2)常用sql语句
补充:MySQL数据库 详解 常用的Mysql数据库操作语句大全 1.连接Mysql 格式: mysql -h主机地址 -u用户名 -p用户密码 1.连接到本机上的MYSQL.首先打开DOS窗口,然后 ...
- 注意:darknet安装
参考:https://github.com/AlexeyAB/darknet # 一.安装 ## linux下安装 - 在darknet目录下执行make ## windows下安装 1. 安装vs2 ...
- ECNU 3260 - 袋鼠妈妈找孩子
题目链接:http://acm.ecnu.edu.cn/problem/3260/ Time limit per test: 1.5 seconds Time limit all tests: 10. ...
- Spark案例分析
一.需求:计算网页访问量前三名 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /* ...
- 一致性 hash 算法(转)
add by zhj:介绍了什么是一致性hash,以及实现一致性hash的一种算法. 原文:http://my.oschina.net/u/195065/blog/193614 目录[-] 一致性 h ...
- usb设备运行不正常的解决方法(转)
add by zhj:上午鼠标还用的好好的,下午就不能用了,插上之后提示“跟这台计算机连接的一个usb设备运行不正常”,尼玛,什么情况?我今天没安装软件啊,换了个USB接口也不行,在另一台电脑上试还是 ...
- qt——QObject 与 QWidget 的区别
QObject是QT中所有类的基类,QWidget是所有UI Widget类的基类,所以QObject是QWidget的基类,从QWidget继承也就表示继承了QObject的所有属性.
- 兼容IE7以上的无缝滚动,带箭头、停顿
<!DOCTYPE HTML><html> <head> <meta charset="utf-8" /> ...