winsock编程(六)

 十二.实例
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退出了聊天室
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值