代码中使用WinSock2函数库,设计并实现了简单的聊天室功能。该程序为命令行程序。对于服务器和客户端,需要:

  1. 服务器:创建监听套接字,并按本地主机绑定;主线程监听并接受来自客户端的请求,并为该客户端创建单独线程;接收与发送消息的事务放在同各客户端的单独线程中处理。
  2. 客户端:创建套接字,并对服务器发起连接;主线程始终处于发送消息的状态;副线程用于不断从服务器接收来自其他客户端的消息。

实验代码及部分说明如下:

  1. 服务器(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;

}

  1. 客户端(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,服务器&客户端)的更多相关文章

  1. Tinychatserver: 一个简易的命令行群聊程序

    这是学习网络编程后写的一个练手的小程序,可以帮助复习socket,I/O复用,非阻塞I/O等知识点. 通过回顾写的过程中遇到的问题的形式记录程序的关键点,最后给出完整程序代码. 0. 功能 编写一个简 ...

  2. Erlang 聊天室程序

    Erlang 聊天室程序( 一) Erlang 聊天室程序(二) 客户端的退出 Erlang 聊天室程序(三) 数据交换格式---json的decode Erlang 聊天室程序(四) 数据交换格式- ...

  3. 设置PATH 环境变量、pyw格式、命令行运行python程序与多重剪贴板

    pyw格式简介: 与py类似,我认为他们俩卫衣的不同就是前者运行时候不显示终端窗口,后者显示 命令行运行python程序: 在我学习python的过程中我通常使用IDLE来运行程序,这一步骤太过繁琐( ...

  4. 基于select的python聊天室程序

    python网络编程具体参考<python select网络编程详细介绍>. 在python中,select函数是一个对底层操作系统的直接访问的接口.它用来监控sockets.files和 ...

  5. windos命令行下的程序编写

    1.命令行下写程序. 写程序一定要用IDE?不,我还可以用记事本呢.呵呵,写程序一定要用记事本?? ———————————————— 命令行下输入copy con test.txt后回车可在相应目录下 ...

  6. ASP.NET 使用application和session对象写的简单聊天室程序

    ASP.Net中有两个重要的对象,一个是application对象,一个是session对象. Application:记录应用程序参数的对象,该对象用于共享应用程序级信息. Session:记录浏览 ...

  7. 高级IO复用应用:聊天室程序

    简单的聊天室程序:客户端从标准输入输入数据后发送给服务端,服务端将用户发送来的数据转发给其它用户.这里采用IO复用poll技术.客户端采用了splice零拷贝.服务端采用了空间换时间(分配超大的用户数 ...

  8. [python]小练习__创建你自己的命令行 地址簿 程序

    创建你自己的命令行 地址簿 程序. 在这个程序中,你可以添加.修改.删除和搜索你的联系人(朋友.家人和同事等等)以及它们的信息(诸如电子邮件地址和/或电话号码). 这些详细信息应该被保存下来以便以后提 ...

  9. 使用 GDB 调试需要命令行参数的程序

    使用 gdb 命令提供的 --args 选项可以调试需要命令行参数的程序,如下: gdb --args a.out arg1 arg2 arg3

随机推荐

  1. 类中main函数的快捷创建

    方法一: 新建类时,选择创建 方法二: 打出main-->Alt + /

  2. noip第16课作业

    1. 猴子吃桃 [问题描述] 猴子第一天摘了若干个桃子,当即吃了一半,还不解馋,又多吃了一个:第二天,吃剩下的桃子的一半,还不过瘾,又多吃了一个:以后每天都吃前一天剩下的一半多一个,到第10天想再吃时 ...

  3. mac版win10装eclipse图标太小了,解决办法(2k显示屏+win10)

    安装了Eclipse并且打开之后,发现图标显示极其细小,肉眼几乎无法看清了.这是由于Eclipse对高分屏没有作适配导致的. Windows 10本身对于高分屏的支持已是相当不错,苏菲4的屏幕分辨率为 ...

  4. 编写Shell脚本

    1.脚本的编写 Shell脚本本身是一个文本文件,这里编写一个简单的程序,在屏幕上显示一行helloworld! 脚本内容如下: #!/bin/bash #显示“Hello world!" ...

  5. spark 中划分stage的思路

    窄依赖指父RDD的每一个分区最多被一个子RDD的分区所用,表现为 一个父RDD的分区对应于一个子RDD的分区 两个父RDD的分区对应于一个子RDD 的分区. 宽依赖指子RDD的每个分区都要依赖于父RD ...

  6. hdu 4901 划分序列使异或和==且和

    http://acm.hdu.edu.cn/showproblem.php?pid=4901 给定一个序列,要求选出两个集合,S和T,要求S中选中的元素的下标都要小于T中元素的下标.并且说S中元素的异 ...

  7. Maven中项目的启动

    在run选项卡中,选择Run Configurations

  8. 数据单位bit byte kb Mb Gb

    8 bit = 1 byte 2^10 bytes = 1 kb 2^10 kb = 1 Mb 2^10 Mb = 1Gb 2^32 bit = 2^2 * 2^10 * 2^10 * 2^10 bi ...

  9. 三、Kubernetes之深入了解Pod

      1.yaml格式的Pod配置文件内容及注解 深入Pod之前,首先我们来了解下Pod的yaml整体文件内容及功能注解. 如下: # yaml格式的pod定义文件完整内容: apiVersion: v ...

  10. Spring下配置几种常用连接池

    1.连接池概述 数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出.对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标.数据库连接池正是 ...