网络程序的实现可以有多种方式,Windows Socket就是其中的一种比较简单的实现方式。Socket是连接应用程序和网络驱动程序的桥梁,Socket在应用程序中创建,通过绑定操作和应用程序建立连接关系。
14.1计算机网络的基本知识
计算机网络是相互连接的独立自主的计算机的集合,最初建立的目的是实现资源的共享。其中比较重要的概念是协议,其目的是为了进行网络中的数据交换而建立的规则、标准或约定。不同的层有不同的协议。
14.1.1ISO/OSI七层参考模型
OSI(Open System Interconnection)参考模型将网络的不同功能分成了七层。由高到底的分层及其功能分别如下:
- 应用层:为用户的网络应用程序提供网络通信服务;
- 表示层:处理被传输数据的表示问题,即信息的语法和语义;
- 会话层:在两个相互通信的应用程序之间建立连接,组织和协调之间的通信;
- 传输层:为源短主机和目的端主机提供可靠的传输服务,隔离网络的上下层协议,使得网络和上下层协议无关;
- 网络层:提供IP寻址和路由,负责找到最佳的数据传输路线;
- 数据链路层:提供介质访问,加强物理层的传输功能;
- 物理层:提供二进制传输,确定在通信信道上如何传输比特流。
七层参考模型知识一个功能上的划分,不是物理上的划分,各层之间是严格的单向依赖,通信实体的对等层之间不容许出现直接通信,下次向上层提供服务,上层使用下层提供的服务。
14.1.2数据封装
一台计算机向另外一台计算机发送数据,必须将该数据打包,打包的过程被称为数据封装。由应用层向下,逐步为数据进行封装,在头部或尾部或同时添加本层的头部或尾部。当数据包到达目标计算机时,再从物理层开始,逐步去除头部或尾部,完成拆分。
14.1.3TCP/IP模型
OSI的七层参考模型比较复杂,通常采用TCP/IP模型,该模型有四个层次与OSI七层模型的对应关系为:
- 应用层:对应应用层+表示层+会话层;
- 传输层:对应传输层;
- 网络层:对应网络层;
- 网络接口层:对应数据链路层+物理层。
14.1.4端口
为了标识通信实体中进行通信的进程,TCP/IP提供了协议端口的概念。端口是一种抽象的软解结构,应用程序通过系统调用和某端口建立连接后,传输层传给该端口的数据都会被相应得应用程序接收,相依进程发送给传输层的数据也通过该端口输出。(端口存在与传输层?)
端口的范围是0~65355,1024以下的端口号通常都被系统应用程序占用。如http的80,ftp的21.
14.1.9套接字Socket的引入
Socket最早出现在UNIX系统中,后来被引入Windows。Windows Socket只支持一个通信区域:网际域,这个域被使用网际协议簇通信的进程使用。
14.2Windows Socket的实现
套接字分为三种类型,其中主要使用的由2种:
流式套接字SOCK_STREAM:提供面向连接,可靠的传输服务,数据无差错,无重复发送,按发送顺序接收,流式套接字是基于TCP协议实现的;
数据报式套接字SOCK_DGRAM:提供无连接服务,数据包独立发送,基于UDP协议;
14.2.1基于TCP的socket编程
基于TCP的socket编程的服务器端的流程如下:
- 创建套接字socket;
- 将套接字绑定到一个本地的端口上bind;
- 将套接字设置为监听模式,准备接收客户端请求listen;
- 等待客户端请求,当请求到达后,接受连接要求,返回一个新的对应于此连接的套接字accept;
- 用返回套接字和客户端进行通信send/recv;
- 返回,等待另一个请求;
- 关闭套接字。
示例代码如下:
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return;
}
//create socket
SOCKET socksrv=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addsrcv;
addsrcv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addsrcv.sin_family=AF_INET;
addsrcv.sin_port=htons(6000);
//bind
bind(socksrv,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));
//listen
listen(socksrv,5);
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
while(1)
{
//wait for client
SOCKET sockConn=accept(socksrv,(SOCKADDR*)&addrClient,&len);
char buf[100];
sprintf(buf,\"welcome %s to www.colsir.com\",inet_ntoa(addrClient.sin_addr));
//send
send(sockConn,buf,strlen(buf)+1,0);
char recvbuf[100];
recv(sockConn,recvbuf,100,0);
printf(\"%s\n\",recvbuf);
closesocket(sockConn);
}
}
基于TCP的socket编程的客户端端的流程如下:
- 创建套接字 socket;
- 向服务器发送请求connect;
- 和服务器端进行通信send/recv;
- 关闭套接字。
示例代码如下:
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return;
}
//create socket
SOCKET sockclient=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addsrcv;
addsrcv.sin_addr.S_un.S_addr=inet_addr(\"127.0.0.1\");
addsrcv.sin_family=AF_INET;
addsrcv.sin_port=htons(6000);
//connect
connect(sockclient,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));
//recv
char recvbuf[100];
recv(sockclient,recvbuf,100,0);
printf(\"%s\n\",recvbuf);
//send
send(sockclient,\"send message\",strlen(\"send message\")+1,0);
closesocket(sockclient);
WSACleanup();
}
14.2.2基于UDP的socket编程
基于UDP的socket编程的服务器端的流程如下:
- 创建套接字socket;
- 将套接字绑定到一个本地地址和端口上bind;
- 等待接收数据,接收完毕之后可以选择返回,等待再一次接收recvfrom;
- 关闭套接字。
示例代码如下:
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return;
}
//create socket
SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM/*UDP*/,0);
SOCKADDR_IN addsrcv;
addsrcv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addsrcv.sin_family=AF_INET;
addsrcv.sin_port=htons(6000);
//bind
bind(sockSrv,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));
//recv
while(1)
{
SOCKADDR_IN addServer;
int len=sizeof(SOCKADDR);
char recvbuf[100];
recvfrom(sockSrv,recvbuf,100,0,(SOCKADDR*)&addServer,&len);
printf(\"%s\n\",recvbuf);
}
closesocket(sockSrv);
WSACleanup();
}
基于UDP的socket编程的客户端端的流程如下:
- 创建套接字 socket;
- 向服务器发送数据sendto;
- 关闭套接字。
示例代码如下:
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return;
}
//create socket
SOCKET sockclient=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addsrcv;
addsrcv.sin_addr.S_un.S_addr=inet_addr(\"127.0.0.1\");
addsrcv.sin_family=AF_INET;
addsrcv.sin_port=htons(6000);
sendto(sockclient,\"hello\",strlen(\"hello\")+1,0,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));
closesocket(sockclient);
WSACleanup();
}
14.3小结
看完了书本上的介绍,把代码照着书本抄了一遍,对于socket编程有了一个初步的认识。真正完成能实用的应用程序要有很远的路。过程中遇到的问题做个简单的小结:
- 因为本程序使用了winsock库中的函数,按照动态链接库的使用规则,需要为程序链接相应的lib文件:ws2_32.lib;
- 在创建TCP和UDP套接字的时候需要注意类型的区别,二者分别对应:SOCKET sockSrv=socket(AF_INET,SOCK_STREAM/*TCP*/,0);和SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM/*UDP*/,0);
- 换行符为”\n”,经常不小心就给写成了”/n”