VC 的网络编程总结
使用VC 的网络编程总结1.套接字编程原理1.1 Client/server通信模型1.2 Windows Sockets规范1.3 套接字1.3.1 套接字定义1.3.2分类1.3.3 套接字的作
使用VC 的网络编程总结
1.套接字编程原理
1.1 Client/server通信模型
1.2 Windows Sockets规范
1.3 套接字
1.3.1 套接字定义
1.3.2分类
1.3.3 套接字的作用
1.3.4端口与地址
1.3.5 套接口属性
2.基本的Windows Sockets API编程
2.1常用函数
2.2 TCP实例
2.3 UDP实例
2.4 Socket 通信阻塞的解决方法
3.MFC 下的Socket 编程的类
3.1 CAsyncSocket类
3.2 CSocket类
3.3 Windows Sockets:带存档的套接字的工作方式
3.4 流式套接字通信的操作顺序
3.5 使用 CAsyncSocket 类
3.6 从套接字类派生
3.7 套接字通知
3.8 一个使用CSocket 类的网络通信实例
3.8.1 服务器端应用程序设计(ServerDemo)
3.8.2 客户端应用程序设计(项目名称ClientDemo)
4.套接字的托管实现
4.1 System::Net ::Sockets 命名空间
4.2 实例:一个新邮件检查器
1.套接字编程原理
一个完整的网间通信进程需要由两个进程组成,并且只能用同一种高层协议。也就是说,不可能通信的一端用TCP ,而另一端用UDP 。一个完整的网络信需要一个五元组来标识:协议、本地地址、本地端口号、远端地址、远端端口号。
1.1 Client/server通信模型
在客户/服务器模式中我们将请求服务的一方称为客户(client ),将提供某种服务的一方称为服务器(server )。
一个服务程序通常在一个众所周知的地址监听对服务的请求,也就是说服务进程一直处于休眠状态,直到一个客户对这个服务的地址提出了连接请求。在这个时刻,服务程序被“惊醒”并且为客户提供服务—对客户的请求作出适当的反应。虽然基于连接的服务是设计客户机/服务器应用程序时的标准,但有些服务也是可以通过无连接的接口提供的。
客户机/服务器的请求/响应过程示意图如下所示。
,
图1 客户/服务器通信模型
通过上面的分析,我们不难理解一个一个完整的网络应用程序包括客户端和服务器两个部分。客户与服务器进程的作用是非对称的,因此编码不同。服务进程一般是等待客户请求而启动的,只要系统运行,该服务进程一直存在,直到终止或强迫终止。
1.2 Windows Sockets规范
Windows Sockets 规范是90年代初Microsoft 公司联合其他几家大公司共同制定的一套在Windows 下的二进制兼容网络编程接口规范。它以U.C.Berkeley 大学BSD UNIX中流行的Socket 接口为基础,主要在其上扩充了一组针对Windows 的扩展库函数,增加了符合Windows 消息驱动特性的网络事件异步选择机制,以使程序员能够充分利用Windows 消息驱动机制进行编程。
Windows Sockets 的用途是将基础网络抽象出来,这样,您不必对网络非常了解,并且您的应用程序可在任何支持套接字的网络上运行。它为应用程序开发者定义了一套简单统一的API ,并让各家网络软件供应商共同遵守。
Windows Sockets规范从90年代初的1.0版本开始,经过不断的完善和发展,目前已经有了Windows Sockets 2版本。值得注意的是,Microsoft 的MFC 库现在只支持Windows Sockets 1版本,不支持Windows Sockets 2版本。
MFC 提供了两个类用以封装Windows Sockets API 。一个是CAsyncSocket 类,它主要是提供给那些具有一定网络编程经验,希望同时拥有Socket API编程的灵活性和类库编程便利性的开发者的。另一个是CSocket 类,它由CAsyncSocket 类派生,它具有更高的抽象化,致力于简化网络编程所需的操作。
1.3 套接字
1.3.1 套接字定义
套接字是一个通信终结点,它是Sockets 应用程序用来在网络上发送或接收数据包的对象。套接字具有类型,与正在运行的进程相关联,并且可以有名称。目前,套接字一般只与使用网际协议组的同一“通信域”中的其他套接字交换数据。使用套接字的应用程序间通信模型如图2所示。

图2 套接字通信模型
1.3.2分类
可用的套接字类型有以下两种:
1.3.2.1流式套接字 (stream )
流式套接字提供没有记录边界的数据流,即字节流。字节流能确保以正确的顺序无重
,复地被送达。

客户机图3 流式套接字(有连接通信) 编程
1.3.2.2 数据报套接字 (UDP )
数据报套接字支持面向记录的数据流,但不能确保能被送达,也无法确保按照发送顺序或不重复。
服务器
创建并初始化套接字客户机创建并初始化套接字
监听来自客户机的请求
向服务器发出请求
进行处理
发送结果给客户端
接收结果
关闭连接关闭连接
图4 数据报套接字(无连接通信) 编程
“有序”指数据包按发送的顺序送达。“不重复”指一个特定的数据包只能获取一次。这两种套接字都是双向的,是可以同时在两个方向上(全双工)进行通信的数据流。
注意 在某些网络协议下(如 XNS ),流可以面向记录,即作为记录流而非字节流。但在更常用的 TCP/IP 协议下,流为字节流。Windows Sockets 提供与基础协议无关的抽象化级别。
1.3.3 套接字的作用
套接字的作用非常大,至少在下面三种通信上下文中如此:
● 客户端/服务器模型。
● 对等网络方案,如聊天应用程序。
● 通过让接收应用程序将消息解释为函数调用来进行远程过程调用 (RPC)。
Remote Procedures Call
1.3.4端口与地址
在网络上,一个套接字的标识主要借助于地址和端口来描述。
套接字的地址指该套接字所在计算机的网络地址,可以为域名或IP 地址的形式。通常,创建套接字时不必指明网络地址,只有在拥有多个网络地址的机器时,才需要显式指定一个网络地址。
同一机器上可以运行多个网络应用程序,每个应用程序都有自己的套接字用以进行网络通信,此时如果只有地址标识套接字,则当一个通信包到达机器时,将无法确定究竟是哪
,个应用程序的套接字需要接收此信息。由此增加了端口的概念,以协助区分同一机器上不同应用程序的套接字。
端口用于标识进程,同一机器上不同的网络应用程序各有不同的端口,这样,通过“网络地址 端口号”的标识方法,便唯一标识了机器上的应用程序了。
某些端口是专门为公共服务保留的(Ftp:21,http:80),除非程序是要提供这些服务,否则应尽量避免使用这些端口。一般来说,端口1024以前的端口号都是系统保留的或是作为公共服务的,应尽量选择大于1024的端口号,以避免冲突。 1.3.5 套接口属性
套接口有一系列的属性用于标识套接口的状态等信息,它们的属性如表1所示。

可以通过getsockopt()函数获取套接口的属性,也可以通过setsockopt()函数设置套接口的属性。
2. 基本的Windows Sockets API编程
● 需要在程序中添加下面的包含语句:#include
在winsock 中,应用程序通过sockaddr_in 结构来指定IP 地址和服务端口信息 sockaddr_in internetAddr; int nPortID=5320;
internetAddr.sin_family=AF_INET;
internet.sin_addr.s_addr=inet_addr(“202.202.42.88”); //INADDR_ANY internet.sin_port=htons(nPortID);
ip 地址不容易记忆,还提供了许多地址和名称解析函数如gethostbyname,gethostbyaddr 等。 2.1常用函数
1)WSAStartup 调用windows Socket DLL 函数原型 int WSAStartup(
WORD wVersionRequested, //应用程序要求的sockets 版本
,LPWSADATA lpWSAData //指向数据结构WSDATA 的指针, //得到windows Socket的具体信息
);
WSDA TA 定义如下:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
#ifdef _WIN64
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpV endorInfo;
char szDescription[WSADESCRIPTION_LEN 1]; char szSystemStatus[WSASYS_STATUS_LEN 1]; #else
char szDescription[WSADESCRIPTION_LEN 1]; char szSystemStatus[WSASYS_STATUS_LEN 1]; unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpV endorInfo;
#endif
} WSADATA, FAR * LPWSADATA;
2)WSACleanup 结束对Windows Sockets DLL的调用
函数原型:int WSACleanup(void);
3)socket 用于建立Sockets 。
函数原型:SOCKET socket(
int af, //地址族,一般是AF_INET
int type , //socket类型,SOCK_STREAM或SOCK_DGRAM int protocol //协议类型,通常取值 0
);
4)closesocket 关闭套接字
函数原型:int closesocket(
SOCKET s //要关闭的套接字
);
5)bind 将一个本地地址和一个SOCKET 描述字连接起来
函数原型:int bind(
SOCKET s, //要绑定的套接字
const struct sockaddr FAR* name, //指向SOCKADDR 结构的地址 int namelen //地址结构的sizeof
)
Tcp/ip SOCKADDR结构
struct sockaddr{
unsigned short sa_family;
char sa_data[4];
};
,struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
6)listen 设定socket 为监听状态
函数原型:int listen(
SOCKET s, //进行监听的socket
int backlog //客户端可以连接的请求个数
);
7)accept 接受一个socket 的连接请求,同时返回一个新的socket ,新的socket 用来在服务器与客户端之间传递和接收信息。
函数原型:SOCKET accept(
SOCKET s, //处于监听状态的socket
struct sockaddr FAR* addr, //将要接受地址的sockaddr 指针
int FAR* addrlen //地址的长度
);
8)connect 连接客户端的socket 到指定的网络服务器。连接成功后,客户端用此socket 与服务器通信。
函数原型:int connect(
SOCKET s, //将要连接的socket
const struct sockaddr FAR* name, //目标socket 地址
int namelen //地址结构sizeof
);
9)recv 用于接收已经建立连接的socket 数据信息
函数原型:int recv(
SOCKET s,
char FAR* buf, //接收数据缓冲区
int len ,//缓冲区长度
int flags //有MSG_PEEK和 MSG_OOB
);
返回值:接收到的字节数
10)send 对已经建立连接的socket 发送数据信息
函数原型:int send(
SOCKET s,
char FAR* buf, //发送数据缓冲区
int len ,//缓冲区长度
int flags //有MSG_PEEK和 MSG_OOB
);
返回值:发送的字节数
11)WSAAsyncSelect 要求socket 在有事件发生时通知使用者,本函数将套接口设置成为非阻塞方式。
函数原型:int WSAAsyncSelect(
,SOCKET s,
HWND hWnd, //接收网络事件的窗口句柄
unsigned int wMsg,//发送给窗口的网络事件消息
long lEvent //网络消息
);
12)sendto 向目标地址发送数据信息
int sendto(
SOCKET s,
const char FAR * buf,
int len,
int flags,
const struct sockaddr FAR * to,
int tolen
);
13)recvfrom 接收目标地址传来的数据信息
int recvfrom(
IN SOCKET s,
OUT char FAR * buf,
IN int len,
IN int flags,
OUT struct sockaddr FAR * from,
IN OUT int FAR * fromlen
);
2.2 TCP 实例
服务器端需要建立两个套接字,一个用于监听连接请求,另一个用来与请求连接的套接字建立连接,实际的数据传送是通过后一个套接字。而客户端只需要一个套接字即可。


窗体版TCP server(阻塞式)
在stdafx.h 文件中加入 #include
//启动TCP server 按钮事件处理
void CTcpServerDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
if (WSAStartup(0x0101,&ws)!=0)
{
m_edit1="WSAStartup() failed!";
UpdateData(false );
return ;
}
//创建套接字
servsock=socket(AF_INET,SOCK_STREAM,0);
//填充服务器地址结构
servport=5555;
memset(&sa,0,sizeof (sa));
sa.sin_family=AF_INET;
sa.sin_port=htons(servport);
sa.sin_addr.s_addr=inet_addr("202.202.42.88");
//绑定套接字到服务器地址结构
err=bind(servsock,(const sockaddr *)&sa,sizeof (sa)); if (err!=0)
{
m_edit1="Bind failed!";
UpdateData(false );
//监听套接字
err=listen(servsock,5);
if (err!=0)
{
m_edit1="Listen failed!";
UpdateData(false ); return ; }

return ;
}
m_edit1.SetString("Waiting request...");
UpdateData(false );
this ->RedrawWindow ();//如不调用此句,则在阻塞Socket 方式下窗体无法正常刷新 //等待连接请求
len=sizeof (cliaddr);
clisock=accept(servsock,(struct sockaddr *)&cliaddr,&len);
m_edit1.Format("Accept
Client:s:dn",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
}
//发送消息事件处理
void CTcpServerDlg::OnBnClickedButton2()
{
//发送欢迎词
send(clisock,buff,strlen(buff),0);
}
//关闭连接事件处理
void CTcpServerDlg::OnBnClickedButton3()
{
// TODO: 在此添加控件通知处理程序代码 //关闭连接 m_edit1="Connection Closed!"; UpdateData(false ); // TODO: 在此添加控件通知处理程序代码 sprintf(buff,"Welcome you s",inet_ntoa(cliaddr.sin_addr)); UpdateData(false );
closesocket(clisock);
closesocket(servsock);
WSACleanup();
}
