您的位置:首页 > 理论基础 > 计算机网络

网络通信程序的开发入门(Winsock)

2010-09-01 13:15 344 查看
 

0.  概念解析

        异步方式

——指的是发送方不等接收方

响应,便接着发下个数据包的通信方式;

        同步方式

——指发送方发出数据后,等收到接收方发回的响应

,才发下一个数据包的通信方式。

    阻塞套接字

——是指执行此套接字的网络调用时,直到成功才返回,否则一直阻塞在此网络调用上,比如调用recv()函数读取网络缓冲区中的数据,如果没有
数据到达,将一直挂在recv()这个函数调用上,直到读到一些数据,此函数调用才返回;

       非阻塞套接字

——是指执行此套接字的网络调用时,不管是否执行成
功,都立即返回

。比如调用recv()函数读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。

       

       在实际Windows网络通
信软件开发中,异步非阻塞套接字

是用的最多的。平常所说的C/S(客户端/服务器)结构的软件就是异步非阻塞模式的。

        MFC提供了一个异步类CAsyncSocket

,它封装了异步、非阻塞Socket的基本
功能,用它做常用的网络通信软件很方便。它屏蔽了Socket的异步、非阻塞等概念,开发人员无需了解异步、非阻塞Socket的原理和工作机制。因
此,初学学习编网络通信程序时,不要用MFC提供的类,而先用Winsock2   API,这样有助于对异步、非阻塞Socket编程机制的理解。

 

================下面是关于Winsocket的知识介绍


====================

 

 
套接字(socket)



 
Socket原意是
插座

”。
应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个

应用程序进程提供并发

服务的问题。多个

TCP连接

或多个应用程序进程

可能需要通过同一个

TCP协议端口

传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket)的接
口。

       

区分不同应用程序进程间的网络通信和连接,主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。通过将这3个参数结合起来,与一个“插座”Socket绑定,应用层就可以和传输层通过套接字接口,区分

来自不同应用程序

进程或网络连接

的通
信,实现数据传输的并发服务。

 
套接字的类型

(1)流式套接字(SOCK_STREAM)——TCP

         提供面向连接,可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接收

 

         基于TCP的socket编程服务器和客户端进行通信都使用send/recv

(2)数据报套接字(SOCK_DGRAM)

        提供无连接服务。数据报以独立包形式发送,不提供错误保证,数据可能丢失或重复,并且接收顺序混乱。基于UDP原始套接字(SOCK_RAW)。

        基于UDP的socket编程服务器端为接收端,客户端为发送端。发送数据为sendto,接收数据为recvfrom

 

其他

套接字存在于通信区域中。通信区域

也叫地址族,它是一个抽象的概念,主要用于将通过套接字通信的进程的共有特性综合在一起。套接字通常只与同一区域的套接字交换数据(也有可能跨区域通信,但这只在执行了某种转换进程后才能实现)。Windows
Sockets
只支持一个通信区域:网际域(AF_INET
),这个域被使用网际协议簇通信的进程使用。

网络字节顺序不同的计算机存放多字节值的顺序不同,有的机器在起始地址存放低字节(低位先存),有的机器在起始地址存放高字节(高位先存)。基于Intel
的CPU
,即我们通常使用的PC
机采用的是低位先存。为保证数据的正确性,在网络协议中需要制定网络字节顺序。TCP/IP
协议使用16
位整数和32
位整数的高位先存格式。

 

客户端/服务器端模型的理由

        网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户机/服务器模式的TCP/IP。

 

1.  一些结构定义和函数

 

(一)  、3个结构定义:
1.SOCKET socket (
int af,       //指定地址族,对于TCP/IP只能是AF_INET(PF_INET)
int type,     //SOCK_STREAM,SOCK_DGRAM
int protocol  //推荐为零,可自动选择协议
);
2. struct sockaddr_in
{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
3. struct in_addr
{
union
{
struct{unsigned char s_b1,s_b2,s_b3,s_b4;} S_un_b;
struct  {unsigned  short  s_w1,s_w2;} S_un_w; unsigned long S_addr;
} S_un;
};
4. struct sockaddr
{
u_short sa_family;
char sa_data[14];
};
(二)   4个函数定义
1. SOCKET accept(
SOCKET s,   //s:套接口描述字,该套接口在listen()后监听连接
struct sockaddr FAR* addr, //addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。
int FAR* addrlen           //必须在传入一个addrlen之前为它赋初始值,否则调用失败
);  //int len=sizeof(SOCKADDR); //addrlen:(可选)指针,指向存有addr地址长度的整形数。
本函数从s的等待连接队列中抽取第一个连接,创建一个与s同类的新的套接口并返回句柄。
2. unsigned long inet_addr(const char FAR* cp); //cp:一个以Internet标准“.”间隔的字符串
本函数将一个点间隔地址转换成一个in_addr(用来把IP地址转化为ULONG类型,用于IN_ADDR结构)。
3, char FAR* inet_ntoa(struct in_addr in);//
本函数将网络地址转换成“.”点隔的字符串格式(返回一个点分十进制地址值)//in:一个表示Internet主机地址的结构
4, int bind(SOCKET s,const struct sockaddr FAR* name,int namelen);//s:标识一未捆绑套接口的描述字
本函数适用于未连接的数据报或流类套接口,在connect()或listen()调用前使用。当用socket()创建套接口后,它便存
在于一个名字空间(地址族)中,但并未赋名。bind()函数通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆
绑(主机地址/端口号)。
在为我们的网络程序指定端口号时,我们要用1024以上的端口.
两个类型转换函数:
(1)htonl 把一个u_long类型从主机字节序转换为网络字节序
(2)htons 把一个u_short类型从主机字节序转换为网络字节序


 

 

2.  两种类型套接字编程


<1> TCP(

Transmission Control Protocol
)流式套接字的编程步骤

服务器端程序:


1、加载套接字库

2、创建套接字(socket)。 

3、将套接字绑定到一个本地地址和端口上(bind)。

4、将套接字设为监听模式,准备接收客户请求(listen)。

5、等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。

6、用返回的套接字和客户端进行通信(send/recv)。

7、返回,等待另一客户请求。

8、关闭套接字。

//服务器端代码如下:
#include <Winsock2.h>//加裁头文件
#include <stdio.h> //加载标准输入输出头文件
void main()
{
WORD wVersionRequested;//版本号
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD( 1, 1 );//1.1版本的套接字

err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}//加载套接字库,加裁失败则返回
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}//如果不是1.1的则退出
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);//创建套接字(socket)。 SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//转换Unsigned short为网络字节序的格式
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//将套接字绑定到一个本地地址和端口上(bind)

listen(sockSrv,5);//将套接字设为监听模式,准备接收客户请求(listen)。
SOCKADDR_IN addrClient;//定义地址族
int len=sizeof(SOCKADDR);//初始化这个参数,这个参数必须被初始化
while(1)
{
SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);//accept的第三个参数一定要有初始值。
//等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。
//此时程序在此发生阻塞
char sendBuf[100];
sprintf(sendBuf,"Welcome %s to http://www.sunxin.org", inet_ntoa(addrClient.sin_addr));
//用返回的套接字和客户端进行通信(send/recv)
send(sockConn,sendBuf,strlen(sendBuf)+1,0);
char recvBuf[100];
recv(sockConn,recvBuf,100,0);
printf("%s/n",recvBuf);
closesocket(sockConn); //关闭套接字。等待另一个用户请求
}
}


客户端程序:


1、加载套接字库

2、创建套接字(socket)。 

3、向服务器发出连接请求(connect)。

4、和服务器端进行通信(send/recv)。

5、关闭套接字。

//客户端代码如下:
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD( 1, 1 );

err = WSAStartup( wVersionRequested, &wsaData );//加载套接字库
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0); //创建套接字(socket)

SOCKADDR_IN addrSrv;
//本地回路地址,不管本地主机上有没有网卡,都可以测试网络
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); //向服务器发出连接请求(connect)。
char recvBuf[100];

//和服务器端进行通信(send/recv)。
recv(sockClient,recvBuf,100,0);
printf("%s/n",recvBuf);
send(sockClient,"This is lisi",strlen("This is lisi")+1,0);
closesocket(sockClient);    //关闭套接字。
WSACleanup();    //必须调用这个函数清除参数
} 


 

<2> UDP(

User Datagram Protocol
)数据报套接字编程步骤

服务器端(接收端)程序:


1、创建套接字(socket)。 

2、将套接字绑定到一个本地地址和端口上(bind)。

3、等待接收数据(recvfrom)。

4、关闭套接字。

//服务器端代码:
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD( 1, 1 );

err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
char recvBuf[100];
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
printf("%s/n",recvBuf);
closesocket(sockSrv);
WSACleanup();
}


 

客户端(发送端)程序:


1、创建套接字(socket)。 

2、向服务器发送数据(sendto)。

3、关闭套接字。

//客户端代码:
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD(1, 1);

err = WSAStartup(wVersionRequested, &wsaData);
if(err != 0){
return;
}
if(LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1){
WSACleanup();
return;
}
SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
sendto(sockClient,"Hello",strlen("Hello")+1,0,
(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
closesocket(sockClient);
WSACleanup();
}


 

注:

 

    TCP和UDP编程代码大致相同;不同之处在于,TCP使用send/recv,UDP使用sendto/recvfrom。

 

 

 

 

3.  实例

=====


异步非阻塞网络通信程序=====

服务器端

(UDP)

#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD( 1, 1 );

err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}

if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
char recvBuf[100];
char sendBuf[100];
char tempBuf[200];
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
while(1)
{
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
if('q'==recvBuf[0])
{
sendto(sockSrv,"q",strlen("q")+1,0,(SOCKADDR*)&addrClient,len);
printf("Chat end!/n");
break;
}
sprintf(tempBuf,"%s say : %s",inet_ntoa(addrClient.sin_addr),recvBuf);
printf("%s/n",tempBuf);
printf("Please input data:/n");
gets(sendBuf);
sendto(sockSrv,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrClient,len);
}
closesocket(sockSrv);
WSACleanup();
}


 

客户端
(UDP)



#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD( 1, 1 );

err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}

if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
char recvBuf[100];
char sendBuf[100];
char tempBuf[200];
int len=sizeof(SOCKADDR);
while(1)
{
printf("Please input data:/n");
gets(sendBuf);
sendto(sockClient,sendBuf,strlen(sendBuf)+1,0,
(SOCKADDR*)&addrSrv,len);
recvfrom(sockClient,recvBuf,100,0,(SOCKADDR*)&addrSrv,&len);
if('q'==recvBuf[0])
{
sendto(sockClient,"q",strlen("q")+1,0,
(SOCKADDR*)&addrSrv,len);
printf("Chat end!/n");
break;
}
sprintf(tempBuf,"%s say : %s",inet_ntoa(addrSrv.sin_addr),recvBuf);
printf("%s/n",tempBuf);
}
closesocket(sockClient);
WSACleanup();
}


 

附注:

      记着要加载库函数
ws2_32.lib
      启动顺序应遵循先服务器后客户机,否则容易出错。

      发送字符时应该多加一个空字符作为结束字符。

 
 
 
参考说明:孙鑫VC学习笔记


 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息