简易的命令行聊天室程序(Winsock,服务器&客户端)
代码中使用WinSock2函数库,设计并实现了简单的聊天室功能。该程序为命令行程序。对于服务器和客户端,需要:
- 服务器:创建监听套接字,并按本地主机绑定;主线程监听并接受来自客户端的请求,并为该客户端创建单独线程;接收与发送消息的事务放在同各客户端的单独线程中处理。
- 客户端:创建套接字,并对服务器发起连接;主线程始终处于发送消息的状态;副线程用于不断从服务器接收来自其他客户端的消息。
实验代码及部分说明如下:
- 服务器(Server.cpp)
#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<vector>
using namespace std;
#define DEFAULT_PORT 7321
#define BUF_SIZE 1024
typedef struct{
SOCKET info_socket_rev;
sockaddr_in info_addr_rev;
} info2thread;
char szMessage[BUF_SIZE];
std::vector<SOCKET> vClientSockets; //用于保存连接中的Client套接字
DWORD WINAPI ThreadProc(PVOID pParam) ;
int main(int argc,char* argv[]){
WSADATA wsd;
SOCKET sListen,sClient;
sockaddr_in local,client;
int iAddrSize;
HANDLE hThread;
DWORD dwThreadId;
info2thread infoParam;
//加载Winsock
if(WSAStartup(MAKEWORD(2,2),&wsd) != 0){
printf("Failed to load Winsock!\n");
exit(1);
}
//创建监听套接字及其地址信息结构体
sListen = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_port = htons(DEFAULT_PORT);
local.sin_family = AF_INET;
//j将套接字和地址信息绑定
if(bind(sListen,(const sockaddr*) &local,sizeof(local)) == SOCKET_ERROR){
printf("bind() failed:%d\n",WSAGetLastError());
return 1;
}
//监听
listen(sListen,7);
printf("Server is Ready, listening on Port:%d.\n>\n",ntohs(local.sin_port));
while(1){
//处理连接请求
iAddrSize = sizeof(client);
sClient = accept(sListen,(sockaddr*)&client,&iAddrSize);
if(sClient == INVALID_SOCKET){
printf("accept() failed:%d\n",WSAGetLastError());
return 1;
}
printf("Accepted Client: %s:%d\n",
inet_ntoa(client.sin_addr), ntohs(client.sin_port));
infoParam.info_addr_rev = client; //infoParam用于构建传给线程的结构体,主要用于IP地址和Port端口的输出
infoParam.info_socket_rev = sClient;
//在vector中保存连接中的客户端Socket
vClientSockets.push_back(sClient);
//创建进程接收数据
hThread = CreateThread(NULL,0,ThreadProc,(LPVOID)&infoParam,0,&dwThreadId);
if (hThread == NULL){
printf("CreateThread() failed: %d\n", GetLastError());
break;
}
CloseHandle(hThread); //不再需要这个句柄,关掉它,但并非是关掉对应线程
}
//关闭
closesocket(sListen);
WSACleanup();
return 0;
}
DWORD WINAPI ThreadProc (PVOID pParam) {
int ret,errCode;
info2thread *pInfo = (info2thread*) pParam;
SOCKET sClient = pInfo->info_socket_rev,
sOther;
sockaddr_in client = pInfo->info_addr_rev;
char szIPPort[BUF_SIZE],
szPort[7];
//构造客户端 "IP:Port" 标识
_itoa(ntohs(client.sin_port),szPort,10); //itoa函数用于将整型数转化成对应的字符串,10表示十进制,末尾自动添'\0'
strcpy(szIPPort,inet_ntoa(client.sin_addr)); //拷贝IP
strcpy(szIPPort+strlen(inet_ntoa(client.sin_addr)),":");
strcpy(szIPPort+strlen(inet_ntoa(client.sin_addr))+1,szPort); //拷贝Port
while(1){
//接收来自客户端的数据
ret = recv(sClient,szMessage,BUF_SIZE,0);
if (ret == SOCKET_ERROR){
errCode = WSAGetLastError();
switch(errCode){
case 10054: //10054:客户端主动退出时的错误代码
printf("%s:%d has exited.\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
break;
default:
printf("recv() failed:%d from %s\n", errCode,szIPPort);
}
break; //跳出while(1)循环
}
szMessage[ret] = '\0';
printf("Server%d REV %s: %s\n", GetCurrentProcessId(),
szIPPort,
szMessage);
//向正在连接的所有其他客户端发送消息(聊天室)
for(vector<SOCKET>::iterator it = vClientSockets.begin();it != vClientSockets.end(); ++it){
if(*it == sClient) continue;
sOther = *it;
//广播客户标识信息
ret = send(sOther,szIPPort,strlen(szIPPort),0);
if (ret == SOCKET_ERROR); //do nothing
//广播客户消息
ret = send(sOther,szMessage,strlen(szMessage),0);
if (ret == SOCKET_ERROR); //do nothing
}
}
//关闭套接字,从vector中删除对应的元素
for(vector<SOCKET>::iterator it = vClientSockets.begin(); it != vClientSockets.end(); ){
if(*it == sClient){
it = vClientSockets.erase(it);
break;
}
else{
++it;
}
}
closesocket(sClient);
return 0;
}
- 客户端(Client.cpp)
#include <WinSock2.h>
#include <stdio.h>
#define DEFAULT_PORT 7321
#define BUF_SIZE 128
char szMessage[BUF_SIZE],
szRecieved[BUF_SIZE];
DWORD WINAPI ThreadProc (PVOID);
int main(int argc, char *argv[]){
WSADATA wsd;
SOCKET sClient;
struct sockaddr_in server;
HANDLE hThread;
DWORD dwThreadId;
int ret;
// 加载Winsock
if(WSAStartup(MAKEWORD(2,2), &wsd) != 0){
printf("Failed to load Winsock library!\n");
return 1;
}
//创建套接字
sClient = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sClient == INVALID_SOCKET){
printf("socket() failed on %d\n",WSAGetLastError());
return 1;
}
//填写服务器地址信息结构体
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_port = htons(DEFAULT_PORT);
server.sin_family = AF_INET;
//查询服务器
//连接
if(connect( sClient,(const sockaddr*)&server,
sizeof(server)) == SOCKET_ERROR){
printf("connect() failed: %d\n", WSAGetLastError());
return 1;
}
else
{
printf("This Client is Connected.\n>\n");
}
//创建一个用于不停接收服务器数据的进程
hThread = CreateThread(NULL,0,ThreadProc,(LPVOID)&sClient,0,&dwThreadId);
if (hThread == NULL){
printf("CreateThread() failed: %d, can't receive message from SERVER!\n", GetLastError());
}
CloseHandle(hThread); //不再需要这个句柄,关掉它,但并非是关掉对应线程
//发送数据
do{
gets_s(szMessage,BUF_SIZE-1);
ret = send(sClient,szMessage,strlen(szMessage),0);
if (ret == SOCKET_ERROR){
printf("send() failed: %d\n", WSAGetLastError());
break;
}
}while(ret);
//关闭套接字
closesocket(sClient);
WSACleanup();
return 0;
}
DWORD WINAPI ThreadProc (PVOID pParam){
SOCKET sRecv = *(SOCKET*)pParam;
int ret;
while(1){
//接收标识
ret = recv(sRecv,szRecieved,BUF_SIZE,0);
if(ret == SOCKET_ERROR){
printf("[Unknown]"); //标识接收失败,则统一认其为Unknown用户
}
else{
szRecieved[ret] = '\0';
printf("[%s]",szRecieved);
}
//分割符
printf(":");
//接收消息
ret = recv(sRecv,szRecieved,BUF_SIZE,0);
if(ret == SOCKET_ERROR){
printf("recv() from server failed: %d\n",WSAGetLastError());
continue;
}
szRecieved[ret] = '\0';
printf("%s\n",szRecieved);
}
return 0;
}
运行结果截图:
简易的命令行聊天室程序(Winsock,服务器&客户端)的更多相关文章
- Tinychatserver: 一个简易的命令行群聊程序
这是学习网络编程后写的一个练手的小程序,可以帮助复习socket,I/O复用,非阻塞I/O等知识点. 通过回顾写的过程中遇到的问题的形式记录程序的关键点,最后给出完整程序代码. 0. 功能 编写一个简 ...
- Erlang 聊天室程序
Erlang 聊天室程序( 一) Erlang 聊天室程序(二) 客户端的退出 Erlang 聊天室程序(三) 数据交换格式---json的decode Erlang 聊天室程序(四) 数据交换格式- ...
- 设置PATH 环境变量、pyw格式、命令行运行python程序与多重剪贴板
pyw格式简介: 与py类似,我认为他们俩卫衣的不同就是前者运行时候不显示终端窗口,后者显示 命令行运行python程序: 在我学习python的过程中我通常使用IDLE来运行程序,这一步骤太过繁琐( ...
- 基于select的python聊天室程序
python网络编程具体参考<python select网络编程详细介绍>. 在python中,select函数是一个对底层操作系统的直接访问的接口.它用来监控sockets.files和 ...
- windos命令行下的程序编写
1.命令行下写程序. 写程序一定要用IDE?不,我还可以用记事本呢.呵呵,写程序一定要用记事本?? ———————————————— 命令行下输入copy con test.txt后回车可在相应目录下 ...
- ASP.NET 使用application和session对象写的简单聊天室程序
ASP.Net中有两个重要的对象,一个是application对象,一个是session对象. Application:记录应用程序参数的对象,该对象用于共享应用程序级信息. Session:记录浏览 ...
- 高级IO复用应用:聊天室程序
简单的聊天室程序:客户端从标准输入输入数据后发送给服务端,服务端将用户发送来的数据转发给其它用户.这里采用IO复用poll技术.客户端采用了splice零拷贝.服务端采用了空间换时间(分配超大的用户数 ...
- [python]小练习__创建你自己的命令行 地址簿 程序
创建你自己的命令行 地址簿 程序. 在这个程序中,你可以添加.修改.删除和搜索你的联系人(朋友.家人和同事等等)以及它们的信息(诸如电子邮件地址和/或电话号码). 这些详细信息应该被保存下来以便以后提 ...
- 使用 GDB 调试需要命令行参数的程序
使用 gdb 命令提供的 --args 选项可以调试需要命令行参数的程序,如下: gdb --args a.out arg1 arg2 arg3
随机推荐
- springMvc里的mvc:resources与静态资源的访问
在进行Spring MVC的配置时,通常我们会配置一个dispatcher servlet用于处理对应的URL.配置如下: <servlet> <servlet-name&g ...
- java基础-day20
第09天 IO流 今日内容介绍 u File类 u 字符流与字节流 第1章 File类 1.1 File概述 打开API,搜索File类.阅读其描述:File文件和目录路径名的抽象表 ...
- 一天学习两个设计模式之Facade模式(外观模式,结构型模式)
程序这东西随着时间推移,程序会越来越大,程序中的类越来越多,而且他们之间相互关联,这会导致程序结构变得越来越复杂.因此我们在使用他们时候,必须要弄清楚他们之间的关系才能使用他们. 特别是在调用大型程序 ...
- 谈谈HashMap线程不安全的体现
原文出处: Hosee HashMap的原理以及如何实现,之前在JDK7与JDK8中HashMap的实现中已经说明了. 那么,为什么说HashMap是线程不安全的呢?它在多线程环境下,会发生什么情况呢 ...
- 编写Shell脚本
1.脚本的编写 Shell脚本本身是一个文本文件,这里编写一个简单的程序,在屏幕上显示一行helloworld! 脚本内容如下: #!/bin/bash #显示“Hello world!" ...
- java并发编程基础-ReentrantLock及LinkedBlockingQueue源码分析
ReentrantLock是一个较为常用的锁对象.在上次分析的uil开源项目中也多次被用到,下面谈谈其概念和基本使用. 概念 一个可重入的互斥锁定 Lock,它具有与使用 synchronized 相 ...
- DXP快捷键记录(红色为经常用到的)
dxp快捷键 1.设计浏览器快捷键:鼠标左击 选择鼠标位置的文档鼠标双击 编辑鼠标位置的文档鼠标右击 ...
- 动态设置和访问cxgrid列的Properties
动态设置和访问cxgrid列的Properties 设置: cxGrid1DBTableView1Column.PropertiesClass = TcxTextEditPropertie ...
- Scott-Knott test 配置与使用
安装最新版本的 R R安装镜像 Download R 3.3.0 for Windows (62 megabytes, 32/64 bit) 理由 Scott-Knott 包由 R 3.2.5 编译的 ...
- 通过JS拦截 pushState 和 replaceState 事件
history.pushState 和 history.replaceState 可以在不刷新当前页面的情况下更改URL,但是这样就无法获取通过AJAX得到的新页面的内容了.虽然各种HTML5文档说 ...