十二.实例
1.任务:
socket和CSocket是有很大的区别的,或者说,socket需要#include <winsock.h>,而CSocket需要的是MFC的支持、消息映射、HWND,需要的是#include<afxsock.h>。
这个实例主要是使用socket函数,熟悉上面十个主题的函数。
n 基本的accept-connect的client/server模型。
n 基本的send/recv的client/server模型。
n 基本的多线程处理,模拟一个多人聊天室,client/server模型。
还是看下一节吧。
2.硬件条件:
服务器端:
ü 操作系统是windows家族,不需要MFC支持。
ü 要有一个固定IP
ü 服务器的路由器或者防火墙要开一个程序设定的端口
ü 也要允许服务器程序访问网络。
注意,关于服务器端的IP:
要有一个固定IP,我这边试过ADSL通过modem的端口映射不行(recv函数说:an application cannot receive from any multicast address until after becoming a group member.)
客户端:
ü 操作系统是windows家族
ü 允许客户端程序访问网络。
3.软件环境:
开发环境是vs studio 6.0。
服务器端口是21(占用的是ftp的端口,建议改成其他端口)。
使用socket版本是2.2。
编译器需要加载wsock32.lib库支持,在Project=>Settings=>Link=>Object/Library modules中添加wsock32.lib。否则连接失败。
4. 基本的accept-connect的client/server模型:
可以在这里下载这个版本的代码:
下载基本的client代码
下载基本的server代码。
server端使用socket的顺序是:
1) WSAStartup() 开始加载socket支持。
2) socket() 新建socket
3) bind() 绑定socket到地址/名称/端口
4) listen() 在端口侦听连接
5) accept() 接受连接,阻塞进程
6) 处理连接
7) WSACleanup() 清理socket资源
client端使用socket的顺序是:
1) WSAStartup() 开始加载socket支持
2) socket() 新建socket
3) connect() 连接到服务器socket
4) 处理连接
5) WSACleanup() 清理socket资源
按照这个顺序,以及参考前面的各个说明,服务器端代码如此写:
//========================================================
//基本的accept-connect的client/server模型
//winlin,2007.1.8,服务器端程序,server.cpp
//========================================================
#include<iostream>
using namespace std;
#include<conio.h>
#include<winsock.h>
//退出函数
void exitSocket()
{
::WSACleanup();
getch();
exit(0);
}
//主函数
void main()
{
//加载socket2.2支持
DWORD wVerRqst;
wVerRqst=MAKEWORD(2,2);
WSAData wsaData;
if(::WSAStartup(wVerRqst,&wsaData) != 0)
{
cout<<"加载socket2.2支持失败."<<endl;
getch();
exit(0);
}
//建立socket
SOCKET skt;
skt=::socket(PF_INET,SOCK_STREAM,0);
if(skt == INVALID_SOCKET)
{
cout<<"创建socket失败."<<endl;
exitSocket();
}
//绑定到本地地址
sockaddr_in sas;//sockaddr server
sas.sin_family=AF_INET;
sas.sin_addr.s_addr=INADDR_ANY;
sas.sin_port=::htons(21);
memset(&sas.sin_zero,0,sizeof(sas.sin_zero));
if(::bind(skt,(sockaddr*)&sas,sizeof(sas)) != 0)
{
cout<<"绑定到本地地址出错."<<endl;
exitSocket();
}
//在本地地址(端口)侦听连接
if(::listen(skt,20) != 0)
{
cout<<"在指定端口侦听出错."<<endl;
exitSocket();
}
//开始接受连接
cout<<"等待客户端连接..."<<endl;
while(true)
{
SOCKET clientSkt;
clientSkt=::accept(skt,NULL,NULL);
if(clientSkt == INVALID_SOCKET)
{
cout<<"接受socket出错."<<endl;
exitSocket();
}
cout<<"接受到一个连接."<<endl;
}
//退出,清理
::WSACleanup();
exit(0);
}
客户端程序如此写:
//========================================================
//基本的accept-connect的client/server模型
//winlin,2007.1.8,客户端程序,client.cpp
//========================================================
#include<iostream>
using namespace std;
#include<conio.h>
#include<winsock.h>
//退出函数
void exitSocket()
{
::WSACleanup();
getch();
exit(0);
}
//主函数
void main()
{
//加载socket2.2支持
DWORD wVerRqst;
wVerRqst=MAKEWORD(2,2);
WSAData wsaData;
if(::WSAStartup(wVerRqst,&wsaData) != 0)
{
cout<<"加载socket2.2支持失败."<<endl;
getch();
exit(0);
}
//建立socket
SOCKET skt;
skt=::socket(PF_INET,SOCK_STREAM,0);
if(skt == INVALID_SOCKET)
{
cout<<"创建socket失败."<<endl;
exitSocket();
}
//指定服务器,并连接到服务器
sockaddr_in sas;
sas.sin_family=AF_INET;
sas.sin_addr.s_addr=::inet_addr("10.1.1.1");//服务器ip地址
sas.sin_port=::htons(21);
memset(&sas.sin_zero,0,sizeof(sas.sin_zero));
if(::connect(skt,(sockaddr*)&sas,sizeof(sas)) != 0)
{
cout<<"连接服务器失败."<<endl;
exitSocket();
}
cout<<"连接服务器成功."<<endl;
getch();
//退出,清理
::WSACleanup();
exit(0);
}
用netstat查看网络连接情况:
C:/>netstat
Active Connections
Proto Local Address Foreign Address State
TCP chengli:ftp chengli:1386 ESTABLISHED
TCP chengli:1386 chengli:ftp ESTABLISHED
可见连接已经建立了,由于端口是21所以显示的是ftp。
5. 基本的send/recv的client/server模型:
可以在这里下载这个版本的代码:
下载send/recv版本的client代码
下载send/recv版本的server代码。
发送和接受数据根据前面的说明就不是很复杂了,只是对于c语言有个通用的:大数据量要先发送它的长度。这是不怎么方便,但是是最好的解决方法,譬如,要想获取一个文件,先传送文件名和文件长度,然后就可以用循环处理了。
在前面的连接已经建立的基础上,客户端先给服务器发送用户输入的用户名,服务器再给客户端发送一条欢迎信息(包含客户端输入的用户名)。
服务器在接受到socket后的代码加入如下的一段:
while(true)
{
SOCKET clientSkt;
clientSkt=::accept(skt,NULL,NULL);
if(clientSkt == INVALID_SOCKET)
{
cout<<"接受socket出错."<<endl;
exitSocket();
}
cout<<"接受到一个连接."<<endl;
//接受客户端输入
char clientUserName[100]={0};
::recv(clientSkt,clientUserName,sizeof(clientUserName),0);
//给客户端发送消息
char info[500]={0};
strcpy(info,"欢迎您,");
strcat(info,clientUserName);
strcat(info,"!这里是winlin大学程序实验室.");
::send(clientSkt,info,strlen(info),0);
}
客户端在建立连接后加入如下代码:
if(::connect(skt,(sockaddr*)&sas,sizeof(sas)) != 0)
{
cout<<"连接服务器失败."<<endl;
exitSocket();
}
cout<<"连接服务器成功."<<endl;
//发送用户名
char userName[100]={0};
cout<<"请输入你的用户名:"<<endl;
cin>>userName;
::send(skt,userName,strlen(userName),0);
//获取服务器的回应
char serverInfo[500]={0};
::recv(skt,serverInfo,sizeof(serverInfo),0);
cout<<"来自服务器的消息:"<<endl
<<serverInfo<<endl;
getch();
//退出,清理
::WSACleanup();
exit(0);
可见还是不很复杂的,结果如下(输入用户名为‘老树’):
连接服务器成功.
请输入你的用户名:
老树
来自服务器的消息:
欢迎您,老树!这里是winlin大学程序实验室.
好了,继续吧。
6. 基本的多线程处理,模拟一个多人聊天室:
可以在这里下载这个版本的代码:
下载聊天室版本的client代码
下载聊天室版本的server代码。
上面的程序明显的问题是,要等第一个用户处理完了才能处理其他连接;噢,这肯定是用多线程的地方了。
这里要建立一个聊天室,大家输入自己的姓名后进入聊天室,然后发送信息给服务器,服务器把消息发送给聊天室的每个人。
服务器也要检测用户的离开,也要通知有新用户加入了。
对这种简单的程序,有一点可以采用:服务器和客户端对话。就是服务器说一句,客户端答一句,反之也是。
服务器显然要一个客户端列表clientList和消息队列msgQueue,一个线程处理客户端对话、一个线程处理消息,看代码吧:
//========================================================
//基本的accept-connect的client/server模型
//winlin,2007.1.8,服务器端程序,server.cpp
//========================================================
#include<iostream>
#include<string>
#include<queue>
#include<list>
using namespace std;
#include<conio.h>
#include<winsock.h>
//消息队列
queue<string> msgQueue;
//用户列表
list<SOCKET> clientList;
//退出函数
void exitSocket()
{
::WSACleanup();
getch();
exit(0);
}
//和客户端对话
void clientHandler(LPVOID pClientSkt)
{
SOCKET skt=(SOCKET)pClientSkt;
//对话:用户名
//接受客户端输入
char clientUserName[100]={0};
::recv(skt,clientUserName,sizeof(clientUserName),0);
//给客户端发送消息
char info[500]={0};
strcpy(info,"欢迎您,");
strcat(info,clientUserName);
strcat(info,"!这里是winlin大学程序实验室.");
::send(skt,info,strlen(info),0);
//添加到用户列表
clientList.push_back(skt);
//通知聊天室有新用户来了
string login="用户";
login.append(clientUserName);
login.append("进入了聊天室.");
msgQueue.push(login);
//接受用户消息
while(true)
{
::Sleep(100);
char msg[500]={0};
if(::recv(skt,msg,sizeof(msg),0) == SOCKET_ERROR)
{
//用户退出
string quit="用户";
quit.append(clientUserName);
quit.append("退出了聊天室.");
msgQueue.push(quit);
//关闭并退出
clientList.remove(skt);
::closesocket(skt);
break;
}
else
{
string smsg="";
smsg.append("来自");
smsg.append(clientUserName);
smsg.append("的消息:/n");
smsg.append(msg);
msgQueue.push(smsg);
}
}
}
//给所有客户端发送消息
void sendMsg()
{
while(true)
{
::Sleep(100);
if(msgQueue.empty())
{
continue;
}
string msg=msgQueue.front();
msgQueue.pop();
for(list<SOCKET>::iterator iter=clientList.begin();
iter!=clientList.end();)
{
SOCKET skt=*(iter );
::send(skt,msg.c_str(),msg.length(),0);
}
}
}
//主函数
void main()
{
//加载socket2.2支持
DWORD wVerRqst;
wVerRqst=MAKEWORD(2,2);
WSAData wsaData;
if(::WSAStartup(wVerRqst,&wsaData) != 0)
{
cout<<"加载socket2.2支持失败."<<endl;
getch();
exit(0);
}
//建立socket
SOCKET skt;
skt=::socket(PF_INET,SOCK_STREAM,0);
if(skt == INVALID_SOCKET)
{
cout<<"创建socket失败."<<endl;
exitSocket();
}
//绑定到本地地址
sockaddr_in sas;//sockaddr server
sas.sin_family=AF_INET;
sas.sin_addr.s_addr=INADDR_ANY;
sas.sin_port=::htons(21);
memset(&sas.sin_zero,0,sizeof(sas.sin_zero));
if(::bind(skt,(sockaddr*)&sas,sizeof(sas)) != 0)
{
cout<<"绑定到本地地址出错."<<endl;
exitSocket();
}
//在本地地址(端口)侦听连接
if(::listen(skt,20) != 0)
{
cout<<"在指定端口侦听出错."<<endl;
exitSocket();
}
//启动消息分发线程
DWORD trdMsgPoster;
::CreateThread(NULL,NULL,
(LPTHREAD_START_ROUTINE)sendMsg,
0,0,&trdMsgPoster);
//开始接受连接
cout<<"等待客户端连接..."<<endl;
while(true)
{
SOCKET clientSkt;
clientSkt=::accept(skt,NULL,NULL);
if(clientSkt == INVALID_SOCKET)
{
cout<<"接受socket出错."<<endl;
exitSocket();
}
cout<<"接受到一个连接."<<endl;
//启动子线程和客户端对话
DWORD trd;
::CreateThread(NULL,NULL,
(LPTHREAD_START_ROUTINE)clientHandler,
(LPVOID)clientSkt,0,&trd);
}
//退出,清理
::WSACleanup();
exit(0);
}
客户端只需要开一个线程来接受服务器消息就可以了,代码:
//========================================================
//基本的accept-connect的client/server模型
//winlin,2007.1.8,客户端程序,client.cpp
//========================================================
#include<iostream>
using namespace std;
#include<conio.h>
#include<winsock.h>
//退出函数
void exitSocket()
{
::WSACleanup();
getch();
exit(0);
}
//接受服务器消息
void receiveMsg(LPVOID pServerSkt)
{
SOCKET skt=(SOCKET)pServerSkt;
while(true)
{
::Sleep(100);
char msg[500]={0};
if(::recv(skt,msg,sizeof(msg),0) == SOCKET_ERROR)
{
cout<<"服务器关闭或者网络错误,程序退出."<<endl;
::closesocket(skt);
exitSocket();
}
else if(strlen(msg)>0)
{
cout<<endl<<msg<<endl;
}
}
}
//主函数
void main()
{
//加载socket2.2支持
DWORD wVerRqst;
wVerRqst=MAKEWORD(2,2);
WSAData wsaData;
if(::WSAStartup(wVerRqst,&wsaData) != 0)
{
cout<<"加载socket2.2支持失败."<<endl;
getch();
exit(0);
}
//建立socket
SOCKET skt;
skt=::socket(PF_INET,SOCK_STREAM,0);
if(skt == INVALID_SOCKET)
{
cout<<"创建socket失败."<<endl;
exitSocket();
}
//指定服务器,并连接到服务器
sockaddr_in sas;
sas.sin_family=AF_INET;
sas.sin_addr.s_addr=::inet_addr("10.1.1.1");//服务器ip地址
sas.sin_port=::htons(21);
memset(&sas.sin_zero,0,sizeof(sas.sin_zero));
if(::connect(skt,(sockaddr*)&sas,sizeof(sas)) != 0)
{
cout<<"连接服务器失败."<<endl;
exitSocket();
}
cout<<"连接服务器成功."<<endl;
//发送用户名
char userName[100]={0};
cout<<"请输入你的用户名:"<<endl;
cin>>userName;
::send(skt,userName,strlen(userName),0);
//获取服务器的回应
char serverInfo[500]={0};
::recv(skt,serverInfo,sizeof(serverInfo),0);
cout<<"来自服务器的消息:"<<endl
<<serverInfo<<endl;
//启动线程接受服务器消息
DWORD trdReceiveMsg;
::CreateThread(NULL,NULL,
(LPTHREAD_START_ROUTINE)receiveMsg,
(LPVOID)skt,0,&trdReceiveMsg);
//开始聊天
while(true)
{
char msg[500]={0};
cout<<"请输入消息:"<<endl;
cin>>msg;
::send(skt,msg,strlen(msg),0);
}
//退出,清理
::WSACleanup();
exit(0);
}
最后的样子是:
服务器端:
等待客户端连接...
接受到一个连接.
接受到一个连接.
客户端1(winlin):
连接服务器成功.
请输入你的用户名:
winlin
来自服务器的消息:
欢迎您,winlin!这里是winlin大学程序实验室.
请输入消息:
用户winlin进入了聊天室.
用户bella进入了聊天室.
来自bella的消息:
hello,我是bella,涂一抹藤.
客户端2(bella):
连接服务器成功.
请输入你的用户名:
bella
来自服务器的消息:
欢迎您,bella!这里是winlin大学程序实验室.
请输入消息:
用户bella进入了聊天室.
hello,我是bella,涂一抹藤.
请输入消息:
来自bella的消息:
hello,我是bella,涂一抹藤.
用户winlin退出了聊天室
1.任务:
socket和CSocket是有很大的区别的,或者说,socket需要#include <winsock.h>,而CSocket需要的是MFC的支持、消息映射、HWND,需要的是#include<afxsock.h>。
这个实例主要是使用socket函数,熟悉上面十个主题的函数。
n 基本的accept-connect的client/server模型。
n 基本的send/recv的client/server模型。
n 基本的多线程处理,模拟一个多人聊天室,client/server模型。
还是看下一节吧。
2.硬件条件:
服务器端:
ü 操作系统是windows家族,不需要MFC支持。
ü 要有一个固定IP
ü 服务器的路由器或者防火墙要开一个程序设定的端口
ü 也要允许服务器程序访问网络。
注意,关于服务器端的IP:
要有一个固定IP,我这边试过ADSL通过modem的端口映射不行(recv函数说:an application cannot receive from any multicast address until after becoming a group member.)
客户端:
ü 操作系统是windows家族
ü 允许客户端程序访问网络。
3.软件环境:
开发环境是vs studio 6.0。
服务器端口是21(占用的是ftp的端口,建议改成其他端口)。
使用socket版本是2.2。
编译器需要加载wsock32.lib库支持,在Project=>Settings=>Link=>Object/Library modules中添加wsock32.lib。否则连接失败。
4. 基本的accept-connect的client/server模型:
可以在这里下载这个版本的代码:
下载基本的client代码
下载基本的server代码。
server端使用socket的顺序是:
1) WSAStartup() 开始加载socket支持。
2) socket() 新建socket
3) bind() 绑定socket到地址/名称/端口
4) listen() 在端口侦听连接
5) accept() 接受连接,阻塞进程
6) 处理连接
7) WSACleanup() 清理socket资源
client端使用socket的顺序是:
1) WSAStartup() 开始加载socket支持
2) socket() 新建socket
3) connect() 连接到服务器socket
4) 处理连接
5) WSACleanup() 清理socket资源
按照这个顺序,以及参考前面的各个说明,服务器端代码如此写:
//========================================================
//基本的accept-connect的client/server模型
//winlin,2007.1.8,服务器端程序,server.cpp
//========================================================
#include<iostream>
using namespace std;
#include<conio.h>
#include<winsock.h>
//退出函数
void exitSocket()
{
::WSACleanup();
getch();
exit(0);
}
//主函数
void main()
{
//加载socket2.2支持
DWORD wVerRqst;
wVerRqst=MAKEWORD(2,2);
WSAData wsaData;
if(::WSAStartup(wVerRqst,&wsaData) != 0)
{
cout<<"加载socket2.2支持失败."<<endl;
getch();
exit(0);
}
//建立socket
SOCKET skt;
skt=::socket(PF_INET,SOCK_STREAM,0);
if(skt == INVALID_SOCKET)
{
cout<<"创建socket失败."<<endl;
exitSocket();
}
//绑定到本地地址
sockaddr_in sas;//sockaddr server
sas.sin_family=AF_INET;
sas.sin_addr.s_addr=INADDR_ANY;
sas.sin_port=::htons(21);
memset(&sas.sin_zero,0,sizeof(sas.sin_zero));
if(::bind(skt,(sockaddr*)&sas,sizeof(sas)) != 0)
{
cout<<"绑定到本地地址出错."<<endl;
exitSocket();
}
//在本地地址(端口)侦听连接
if(::listen(skt,20) != 0)
{
cout<<"在指定端口侦听出错."<<endl;
exitSocket();
}
//开始接受连接
cout<<"等待客户端连接..."<<endl;
while(true)
{
SOCKET clientSkt;
clientSkt=::accept(skt,NULL,NULL);
if(clientSkt == INVALID_SOCKET)
{
cout<<"接受socket出错."<<endl;
exitSocket();
}
cout<<"接受到一个连接."<<endl;
}
//退出,清理
::WSACleanup();
exit(0);
}
客户端程序如此写:
//========================================================
//基本的accept-connect的client/server模型
//winlin,2007.1.8,客户端程序,client.cpp
//========================================================
#include<iostream>
using namespace std;
#include<conio.h>
#include<winsock.h>
//退出函数
void exitSocket()
{
::WSACleanup();
getch();
exit(0);
}
//主函数
void main()
{
//加载socket2.2支持
DWORD wVerRqst;
wVerRqst=MAKEWORD(2,2);
WSAData wsaData;
if(::WSAStartup(wVerRqst,&wsaData) != 0)
{
cout<<"加载socket2.2支持失败."<<endl;
getch();
exit(0);
}
//建立socket
SOCKET skt;
skt=::socket(PF_INET,SOCK_STREAM,0);
if(skt == INVALID_SOCKET)
{
cout<<"创建socket失败."<<endl;
exitSocket();
}
//指定服务器,并连接到服务器
sockaddr_in sas;
sas.sin_family=AF_INET;
sas.sin_addr.s_addr=::inet_addr("10.1.1.1");//服务器ip地址
sas.sin_port=::htons(21);
memset(&sas.sin_zero,0,sizeof(sas.sin_zero));
if(::connect(skt,(sockaddr*)&sas,sizeof(sas)) != 0)
{
cout<<"连接服务器失败."<<endl;
exitSocket();
}
cout<<"连接服务器成功."<<endl;
getch();
//退出,清理
::WSACleanup();
exit(0);
}
用netstat查看网络连接情况:
C:/>netstat
Active Connections
Proto Local Address Foreign Address State
TCP chengli:ftp chengli:1386 ESTABLISHED
TCP chengli:1386 chengli:ftp ESTABLISHED
可见连接已经建立了,由于端口是21所以显示的是ftp。
5. 基本的send/recv的client/server模型:
可以在这里下载这个版本的代码:
下载send/recv版本的client代码
下载send/recv版本的server代码。
发送和接受数据根据前面的说明就不是很复杂了,只是对于c语言有个通用的:大数据量要先发送它的长度。这是不怎么方便,但是是最好的解决方法,譬如,要想获取一个文件,先传送文件名和文件长度,然后就可以用循环处理了。
在前面的连接已经建立的基础上,客户端先给服务器发送用户输入的用户名,服务器再给客户端发送一条欢迎信息(包含客户端输入的用户名)。
服务器在接受到socket后的代码加入如下的一段:
while(true)
{
SOCKET clientSkt;
clientSkt=::accept(skt,NULL,NULL);
if(clientSkt == INVALID_SOCKET)
{
cout<<"接受socket出错."<<endl;
exitSocket();
}
cout<<"接受到一个连接."<<endl;
//接受客户端输入
char clientUserName[100]={0};
::recv(clientSkt,clientUserName,sizeof(clientUserName),0);
//给客户端发送消息
char info[500]={0};
strcpy(info,"欢迎您,");
strcat(info,clientUserName);
strcat(info,"!这里是winlin大学程序实验室.");
::send(clientSkt,info,strlen(info),0);
}
客户端在建立连接后加入如下代码:
if(::connect(skt,(sockaddr*)&sas,sizeof(sas)) != 0)
{
cout<<"连接服务器失败."<<endl;
exitSocket();
}
cout<<"连接服务器成功."<<endl;
//发送用户名
char userName[100]={0};
cout<<"请输入你的用户名:"<<endl;
cin>>userName;
::send(skt,userName,strlen(userName),0);
//获取服务器的回应
char serverInfo[500]={0};
::recv(skt,serverInfo,sizeof(serverInfo),0);
cout<<"来自服务器的消息:"<<endl
<<serverInfo<<endl;
getch();
//退出,清理
::WSACleanup();
exit(0);
可见还是不很复杂的,结果如下(输入用户名为‘老树’):
连接服务器成功.
请输入你的用户名:
老树
来自服务器的消息:
欢迎您,老树!这里是winlin大学程序实验室.
好了,继续吧。
6. 基本的多线程处理,模拟一个多人聊天室:
可以在这里下载这个版本的代码:
下载聊天室版本的client代码
下载聊天室版本的server代码。
上面的程序明显的问题是,要等第一个用户处理完了才能处理其他连接;噢,这肯定是用多线程的地方了。
这里要建立一个聊天室,大家输入自己的姓名后进入聊天室,然后发送信息给服务器,服务器把消息发送给聊天室的每个人。
服务器也要检测用户的离开,也要通知有新用户加入了。
对这种简单的程序,有一点可以采用:服务器和客户端对话。就是服务器说一句,客户端答一句,反之也是。
服务器显然要一个客户端列表clientList和消息队列msgQueue,一个线程处理客户端对话、一个线程处理消息,看代码吧:
//========================================================
//基本的accept-connect的client/server模型
//winlin,2007.1.8,服务器端程序,server.cpp
//========================================================
#include<iostream>
#include<string>
#include<queue>
#include<list>
using namespace std;
#include<conio.h>
#include<winsock.h>
//消息队列
queue<string> msgQueue;
//用户列表
list<SOCKET> clientList;
//退出函数
void exitSocket()
{
::WSACleanup();
getch();
exit(0);
}
//和客户端对话
void clientHandler(LPVOID pClientSkt)
{
SOCKET skt=(SOCKET)pClientSkt;
//对话:用户名
//接受客户端输入
char clientUserName[100]={0};
::recv(skt,clientUserName,sizeof(clientUserName),0);
//给客户端发送消息
char info[500]={0};
strcpy(info,"欢迎您,");
strcat(info,clientUserName);
strcat(info,"!这里是winlin大学程序实验室.");
::send(skt,info,strlen(info),0);
//添加到用户列表
clientList.push_back(skt);
//通知聊天室有新用户来了
string login="用户";
login.append(clientUserName);
login.append("进入了聊天室.");
msgQueue.push(login);
//接受用户消息
while(true)
{
::Sleep(100);
char msg[500]={0};
if(::recv(skt,msg,sizeof(msg),0) == SOCKET_ERROR)
{
//用户退出
string quit="用户";
quit.append(clientUserName);
quit.append("退出了聊天室.");
msgQueue.push(quit);
//关闭并退出
clientList.remove(skt);
::closesocket(skt);
break;
}
else
{
string smsg="";
smsg.append("来自");
smsg.append(clientUserName);
smsg.append("的消息:/n");
smsg.append(msg);
msgQueue.push(smsg);
}
}
}
//给所有客户端发送消息
void sendMsg()
{
while(true)
{
::Sleep(100);
if(msgQueue.empty())
{
continue;
}
string msg=msgQueue.front();
msgQueue.pop();
for(list<SOCKET>::iterator iter=clientList.begin();
iter!=clientList.end();)
{
SOCKET skt=*(iter );
::send(skt,msg.c_str(),msg.length(),0);
}
}
}
//主函数
void main()
{
//加载socket2.2支持
DWORD wVerRqst;
wVerRqst=MAKEWORD(2,2);
WSAData wsaData;
if(::WSAStartup(wVerRqst,&wsaData) != 0)
{
cout<<"加载socket2.2支持失败."<<endl;
getch();
exit(0);
}
//建立socket
SOCKET skt;
skt=::socket(PF_INET,SOCK_STREAM,0);
if(skt == INVALID_SOCKET)
{
cout<<"创建socket失败."<<endl;
exitSocket();
}
//绑定到本地地址
sockaddr_in sas;//sockaddr server
sas.sin_family=AF_INET;
sas.sin_addr.s_addr=INADDR_ANY;
sas.sin_port=::htons(21);
memset(&sas.sin_zero,0,sizeof(sas.sin_zero));
if(::bind(skt,(sockaddr*)&sas,sizeof(sas)) != 0)
{
cout<<"绑定到本地地址出错."<<endl;
exitSocket();
}
//在本地地址(端口)侦听连接
if(::listen(skt,20) != 0)
{
cout<<"在指定端口侦听出错."<<endl;
exitSocket();
}
//启动消息分发线程
DWORD trdMsgPoster;
::CreateThread(NULL,NULL,
(LPTHREAD_START_ROUTINE)sendMsg,
0,0,&trdMsgPoster);
//开始接受连接
cout<<"等待客户端连接..."<<endl;
while(true)
{
SOCKET clientSkt;
clientSkt=::accept(skt,NULL,NULL);
if(clientSkt == INVALID_SOCKET)
{
cout<<"接受socket出错."<<endl;
exitSocket();
}
cout<<"接受到一个连接."<<endl;
//启动子线程和客户端对话
DWORD trd;
::CreateThread(NULL,NULL,
(LPTHREAD_START_ROUTINE)clientHandler,
(LPVOID)clientSkt,0,&trd);
}
//退出,清理
::WSACleanup();
exit(0);
}
客户端只需要开一个线程来接受服务器消息就可以了,代码:
//========================================================
//基本的accept-connect的client/server模型
//winlin,2007.1.8,客户端程序,client.cpp
//========================================================
#include<iostream>
using namespace std;
#include<conio.h>
#include<winsock.h>
//退出函数
void exitSocket()
{
::WSACleanup();
getch();
exit(0);
}
//接受服务器消息
void receiveMsg(LPVOID pServerSkt)
{
SOCKET skt=(SOCKET)pServerSkt;
while(true)
{
::Sleep(100);
char msg[500]={0};
if(::recv(skt,msg,sizeof(msg),0) == SOCKET_ERROR)
{
cout<<"服务器关闭或者网络错误,程序退出."<<endl;
::closesocket(skt);
exitSocket();
}
else if(strlen(msg)>0)
{
cout<<endl<<msg<<endl;
}
}
}
//主函数
void main()
{
//加载socket2.2支持
DWORD wVerRqst;
wVerRqst=MAKEWORD(2,2);
WSAData wsaData;
if(::WSAStartup(wVerRqst,&wsaData) != 0)
{
cout<<"加载socket2.2支持失败."<<endl;
getch();
exit(0);
}
//建立socket
SOCKET skt;
skt=::socket(PF_INET,SOCK_STREAM,0);
if(skt == INVALID_SOCKET)
{
cout<<"创建socket失败."<<endl;
exitSocket();
}
//指定服务器,并连接到服务器
sockaddr_in sas;
sas.sin_family=AF_INET;
sas.sin_addr.s_addr=::inet_addr("10.1.1.1");//服务器ip地址
sas.sin_port=::htons(21);
memset(&sas.sin_zero,0,sizeof(sas.sin_zero));
if(::connect(skt,(sockaddr*)&sas,sizeof(sas)) != 0)
{
cout<<"连接服务器失败."<<endl;
exitSocket();
}
cout<<"连接服务器成功."<<endl;
//发送用户名
char userName[100]={0};
cout<<"请输入你的用户名:"<<endl;
cin>>userName;
::send(skt,userName,strlen(userName),0);
//获取服务器的回应
char serverInfo[500]={0};
::recv(skt,serverInfo,sizeof(serverInfo),0);
cout<<"来自服务器的消息:"<<endl
<<serverInfo<<endl;
//启动线程接受服务器消息
DWORD trdReceiveMsg;
::CreateThread(NULL,NULL,
(LPTHREAD_START_ROUTINE)receiveMsg,
(LPVOID)skt,0,&trdReceiveMsg);
//开始聊天
while(true)
{
char msg[500]={0};
cout<<"请输入消息:"<<endl;
cin>>msg;
::send(skt,msg,strlen(msg),0);
}
//退出,清理
::WSACleanup();
exit(0);
}
最后的样子是:
服务器端:
等待客户端连接...
接受到一个连接.
接受到一个连接.
客户端1(winlin):
连接服务器成功.
请输入你的用户名:
winlin
来自服务器的消息:
欢迎您,winlin!这里是winlin大学程序实验室.
请输入消息:
用户winlin进入了聊天室.
用户bella进入了聊天室.
来自bella的消息:
hello,我是bella,涂一抹藤.
客户端2(bella):
连接服务器成功.
请输入你的用户名:
bella
来自服务器的消息:
欢迎您,bella!这里是winlin大学程序实验室.
请输入消息:
用户bella进入了聊天室.
hello,我是bella,涂一抹藤.
请输入消息:
来自bella的消息:
hello,我是bella,涂一抹藤.
用户winlin退出了聊天室