Winsock编程接口.ppt
,第二章 Winsock编程接口,Windows套接字简介字节顺序和Winsock的寻址方式 Winsock API 基本函数 数据报套接字编程流式套接字编程,1.Windows 套接字简介,Winsock来源,Sockets本来是Unix操作系统上流行的一种网络编程接口(API)。API:Application Programming Interface发展过程:1983年在Berkeley大学4.2BSD UNIX中首先使用,因此被称为Berkeley Socket API(只支持TCP/IP协议)。后来在4.3BSD UNIX中增加了对OSI网络协议的支持。应用平台:UNIX、LINUX产生Winsock:Winsock API在1991年根据4.3BSD UNIX的Berkeley Socket制定的。,Winsock规范,Winsock 规范:一套开放的、支持多种协议的Windows下的网络编程接口。主要支持TCP/IP协议。Winsock 规范主要包括Windows提供的API函数:符合Berkeley Socket风格的库函数;针对Windows的一组扩展库函数。发展过程:1991年的1.0版到1997年的2.2版(主要扩充了对其它协议的支持)。应用平台:Windows系列,Winsock实现库两个版本:Winsock1和Winsock2。现在大都使用Winsock2,Winsock2库的最新版本是2.2。,Winsock实现库,采用Winsock 1的应用必须包含winsock.h 头文件。使用Winsock 2的应用必须包含winsock2.h 头文件。,不同平台对应不同的库版本。,套接字基本概念,套接字可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点。,在编写网络应用程序时,应先申请套接字,以后两台机器上的程序通过该套接字进行通信。,在网络程序编程中,通过将IP、端口与一个套接字绑定,并指明通信协议,从而来实现应用程序与TCPIP协议的交互。,套接字通信模型,客户端,服务器,基于TCP/IP的网络,中间环节,套接字可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点。如下图:,套接字中的信息通过驱动程序送入网卡,然后经网络发送到远端服务器,网卡将接收到的信息通过驱动程序送入套接字,进程C,进程S,套接字(Socket)分类,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),打电话,在TCP/IP协议组中,使用TCP协议来实现流式套接字。,发信,在TCP/IP协议组中,使用UDP协议来实现数据报套接字。,该套接字允许对底层协议(如IP或ICMP)进行直接访问,常用来测试新安装的网络设备,以及对流经网卡的数据进行捕获分析。,它定义了一种无连接、不可靠的双向数据传输服务。,它定义了一种可靠的、面向连接的双向数据传输服务。,2.字节顺序和Winsock的寻址方式,字节顺序含义:占内存多于一个字节类型的数据在内存中的存储顺序。通常有如下两种存储顺序:1 Little endian:将低序字节存储在起始地址(低序优先)2 Big endian:将高序字节存储在起始地址(高序优先),内存地址 1000 1001 1002 1003,低序优先存储顺序:0 x78 0 x56 0 x34 0 x12,高序优先存储顺序:0 x12 0 x34 0 x56 0 x78,然而处于主机上的数据却因不同的CPU上运行的操作系统的不同,字节顺序也不同,参见下表。,通常根据所处的位置不同,低序优先字节顺序和高序优先字节顺序我们又分别称为主机字节顺序和网络字节顺序。,处理器 操作系统 字节排序HP-PA NT 低序优先HP-PA UNIX 高序优先Intelx86 全部 低序优先 Motorola680 x()全部 高序优先,因此,我们常说的主机字节顺序,就是低序优先,但主机上的字节顺序却不尽都是低序优先。,网络字节顺序是TCP/IP中规定的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。,不同字节顺序转换函数 主机字节顺序 hhost;网络字节顺序 nnetworku_short htons(u_short hostshort);把一个u_short 型的主机字节顺序存储的数转换为网络字节顺序存储字节数转换u_long htonl(u_long hostlong);把一个u_long型的主机字节顺序存储的数转换为网络字节顺序存储 字节数转换u_short ntohs(u_short netshort);把一个u_short 型的网络字节顺序存储的数转换为主机字节顺序存储字节数转换u_long ntohl(u_long netlong);把一个u_long型的网络字节顺序存储的数转换为主机字节顺序存储字节数转换,Winsock 寻址Winsock要兼容多个协议,所以必须使用通用的寻址方式。TCP/IP(IP地址,端口)来指定一个地址,其它协议不一定如此。sockaddr结构,struct sockaddr u_short sa_family;/2个字节 共16位 char sa_data14;/14个字节;/共16个字节,sa_family:AF_UNIXUnix internal protocols AF_INETInternet protocols常用 本课程使用 AF_NSXerox NS protocols AF_IMPLINKIMP link layer其中,AF_ 代表“address family”sa_data 在不同的地址家族中存放的数据不同。,sockaddr_in结构(Winsock定义的sockaddr结构的TCP/IP版本)本质和sockaddr是相同的结构(大小一致)sockaddr_in更容易操作。,struct sockaddr_in short sin_family;/Address family 2字节u_short sin_port;/16-bit port number 2字节struct in_addr sin_addr;/32-bit(IP)地址4字节char sin_zero 8;/unused 8字节;/共16个字节该结构与sockaddr兼容,供用户填入参数,sockaddr_in结构 成员说明。,sin_family:必须设置为AF_INET,它告诉Winsock程序使用的是IP地址家族,sin_port:指定TCP或UDP通信服务的端口。0-1023:公共服务使用。如80为HTTP服务的端口 1024-65535:可以由普通用户使用。,struct in_addr sin_addr:IP地址。,sin_zero:为了和sockaddr保持一致。,in_addr结构 在in_addr中采用共用体类型。,struct in_addr union/共用体 struct u_char s_b1,s_b2,s_b3,s_b4;S_un_b;/1+1+1+1=4字节 struct u_short s_w1,s_w2;S_un_w;/2+2=4字节 u_long S_addr;/4字节 S_un;struct in_addr sin_addr;/sockaddr_in结构中的IP地址,共用体:几个不同的变量共占一段内存的结构称为“共用体”。共用体变量所占的内存长度等于最长的成员的长度。这里共用体的三个变量长度一致。,点分 十进制记法,11000000 10101000 00000001 00001100,192.168.1.12,0 xC0,0 xA8,0 x01,0 x0C,IP地址设置,对地址进行设置:注意要把多字节数转换成网络字节顺序 假设有IP地址“192.168.1.12”4字节数据十进制表示 对于struct in_addr sin_addr可以有如下赋值方法:第一种方法(每个数都是单字节,不用转换)sin_addr.S_un.S_un_b.s_b1=192;sin_addr.S_un.S_un_b.s_b2=168;sin_addr.S_un.S_un_b.s_b3=1;sin_addr.S_un.S_un_b.s_b4=12;,第二种方法(转换成网络字节顺序 即高位在前)192 168 1 12 对应的16进制 0 xC0 0 xA8 0 x01 0 x0C 192 168 2字节16进制 0 xC0A8 1 12 2字节16进制 0 x010C sin_addr.S_un.S_un_w.s_w1=htons(12*16*16*16+10*16+8);sin_addr.S_un.S_un_w.s_w2=htons(1*16*16+12);,第三种方法(转换成网络字节顺序 即高位在前)192.168.1.12 4字节16进制 0 xC0A8010C=htonl(12*pow(16,7)+10*pow(16,5)+8*pow(16,4)+1*pow(16,2)+12);,IP地址转换函数(点分十进制IP地址 网络字节顺序IP地址),char*inet_ntoa(struct in_addr in);把一个(Ipv4)32位网络字节顺序IP地址转换成”格式的字符串unsigned long inet_addr(const char*cp);把一个”格式的IP地址字符串转换为(Ipv4)32位网络字节顺序IP,因此可以有更简便的方法,也是最常用的方法=inet_addr(192.168.1.12);,struct sockaddr_in short sin_family;/Address family 2字节u_short sin_port;/16-bit port number 2字节struct in_addr sin_addr;/32-bit(IP)地址4字节char sin_zero 8;/unused 8字节;/共16个字节,下面初始化一个sockaddr_in结构,sockaddr_in addrSrv;addrSrv.sin_family=AF_INET;/把16位主机字节转换为网络字节addrSrv.sin_port=htons(6789);/把点分制转换为32位网络字节=inet_addr(127.0.0.1);,总 结,套接字的基本概念套接字的通信模型Winsock的寻址方式主机字节顺序(低位在前)网络字节顺序(高位在前)两种顺序间的转换IP地址设置,3.Winsock API基本函数,Winsock API环境准备#include/包含了绝大部分socket函数和相关结构类型的声明#pragma comment(lib,WS2_32)/链接到WS2_32.lib,打开WinsockWSAStartup()加载可用的Winsock 实现库函数形式int WSAStartup(WORD wVersionRequested,/传入参数 LPWSADATA lpWSAData/传出参数);/typedef unsigned short WORD;/2个字节 16位功能说明:检查系统中是否有可用的Winsock 实现库,并加载该实现库。网络应用程序开始必须调用此函数。,参数说明:,WORD wVersionRequested含义:用来指定想要加载的Winsock库的版本。表示一个字(2个字节)。高位字节指定副版本,低位字节制定主版本。目前的Win32平台而言,Winsock2库的最新版本是2.2(Win95为Winsock1.1),参数设定:假设加载Winsock2.2版。指定此参数的值为0 x0202;wVersionRequested=0 x0202 或使用宏MAKEWORD(x,y):通过宏MAKEWORD来指定 参数的值。x是高位字节,y是低位字节 MAKEWORD(2,2)指定版本为Winsock2.2版,typedef struct WSAData WORD wVersion;/调用者希望使用的版本号 WORD wHighVersion;/库文件支持的最高版本 char szDescriptionWSADESCRIPTION_LEN+1;/Winsock库描述字符串 如 Winsock2.0库 char szSystemStatusWSASYS_STATUS_LEN+1;/系统状态字符串 如:Running unsigned short iMaxSockets;/Winsock2库已废弃参数 unsigned short iMaxUdpDg;/Winsock2库已废弃参数 char*lpVendorInfo;/Winsock2库已废弃参数 WSADATA,*LPWSADATA;,LPWSADATA lpWSAData 一个指向WSADATA结构的指针,用来返回加载库的详细信息。,WSADATA*lpWSAData;等价于 LPWSADATA lpWSAData;,返回0则正确,否则返回错误。错误代码可以通过WSAGetLastError()来获取。下面只举几个例子说明。更多信息可以MSDN获取,也可到winsock.h或 winsock2.h中查找。如:WSASYSNOTREADY:10091 加载的Winsock 库不存在 WSAEFAULT:10014 lpWSAData参数是无效的指针,返回值:,终止使用WinsockWSACleanup()函数形式:int WSACleanup();功能说明:该函数终止应用程序对Winsock 库的使用,释放加载的Winsock库。每一个WSAStartup的调用必须对应一个WSACleanup的调用。返回值:调用成功返回0;否则SOCKET_ERROR被返回,具体错误可以通过函数 WSAGetLastError()来获取。SOCKET_ERROR是系统宏定义值为-1。,#include/标准输入输出系统文件#include/winsock声明文件#pragma comment(lib,WS2_32)/链接到WS2_32.libvoid main()/通过载入Winsock库,来使用Winsock的相关函数 WSADATA wsaData;/用来返回Winsock库的详细信息 WORD version=MAKEWORD(2,2);/wVersionRequested=0 x0202;int ret=WSAStartup(version,加载释放举例:,WSADATA,/*进行版本号匹配的检查*/if(LOBYTE(wsaData.wVersion)!=2|/取低位字节 主版本 HIBYTE(wsaData.wVersion)!=2)/取高位字节 幅版本 printf(Winsock库版本不匹配n);WSACleanup();/释放库return;printf(“wVersion=%dn”,wsaData.wVersion);/希望使用版本printf(“wHighVersion=%dn”,wsaData.wHighVersion);/最高版本 printf(“szDescription=%sn”,wsaData.szDescription);/库描述printf(“szSystemStatus=%sn”,wsaData.szSystemStatus);/系统状态 WSACleanup();/释放库,wVersion=514wHighVersion=514szDescription=WinSock 2.0szSystemStatus=Running,514说明版本号是2.2 0 x0202:十进制 21616+2=514WinSock 2.0指 WinSock 2库,WSADATA,创建套接字socket(),函数形式:SOCKET socket(int af,/说明套接字使用的协议地址族 int type,/描述套接字的协议类型 int protocol/说明套接字使用的特定协议字段);,为应用程序创建套接字,返回值SOCKET:系统提供的类型定义 typedef u_int SOCKET;调用成功:返回新创建的套接字描述符(一个编号)。调用失败:返回INVALID_SOCKET(系统宏定义值0)。可以调用WSAGetLastError()来获取具体错误的代码。创建套接字后系统会为其分配相应的系统缓冲区。,举例说明:流式和数据报套接字中协议字段指定0时,取缺省的协议SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);/等同上一行 SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);/等同上一行 SOCKET sockSrv=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);SOCKET sockSrv=socket(AF_INET,SOCK_RAW,IPPROTO_IP);,关闭套接字closesocket(),函数形式:int closesocket(SOCKET s);,关闭一个不再使用的套接字。,调用成功:返回0;调用失败:返回SOCKET_ERROR(系统宏定义,值为-1)具体错误代码:调用WSAGetLastError()来获取。,绑定套接字到指定的Socket地址bind(),函数形式:int bind(SOCKET s,const struct sockaddr*name,/Socket地址 int namelen/指定地址参数(name)的长度);,把一个套接字与一个Socket地址联系起来。,调用成功返回0,否则将返回SOCKET_ERROR,SOCKET sListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);sockaddr_in addrSrv;/地址名字/进行绑定 bind(监听套接字,地址,地址长度);if(SOCKET_ERROR=bind(sListen,(SOCKADDR*),sockaddr_in addrSrv;/地址名字addrSrv.sin_family=AF_INET;/协议地址族int PORT=4000;addrSrv.sin_port=htons(PORT);=inet_addr(127.0.0.1);,监听连接listen(),函数形式:int listen(SOCKET s,int backlog/指定正在等待连接的最大队列长度);,设置套接字进入监听连接请求的状态,以准备接收客户发出的连接请求。,调用成功返回0,否则将返回SOCKET_ERROR,/进入监听状态if(SOCKET_ERROR=listen(sListen,5)/5表示等待连接队列的长度printf(监听失败:%dn,WSAGetLastError();return;,请求连接connect(),函数形式:int connect(SOCKET s,const struct sockaddr*name,int namelen);,在客户端当一个套接字建立好之后,就要调用connect()函数,向服务器端提出连接请求。,调用成功返回0,否则将返回SOCKET_ERROR,使用该函数请求建立连接时,将激活建立连接的三次握手,用来建立一条与服务器的TCP连接。,/connect(客户端套接字,服务端地址,服务端地址长度);if(SOCKET_ERROR=connect(sockClient,(sockaddr*),接受连接accept(),函数形式:SOCKET accept(SOCKET s,struct sockaddr*addr,/存放发出连接请求的客户机的地址 int*addrlen/指出客户机套接字地址的长度);,服务器端调用accept()函数来接受来自客户端的连接请求。,调用失败:返回INVALID_SOCKET;调用成功:返回一个新建套接字。新建套接字 客户机套接字原来的监听套接字,仍然处于监听连接状态。,该函数只适用于TCP服务器。缺省是阻塞函数,若队列中无连接请求,则阻塞等待,直到有连接请求,才继续执行。,SOCKET SockConn;/声明和客户端连接的套接字SOCKADDR_IN addrClient;/来自客户端的地址int addrlen=sizeof(addrClient);/初始化客户地址长度参数SockConn=accept(sListen,(SOCKADDR*),在已建立连接的套接字上发送数据send(),函数形式:int send(SOCKET s,const char*buf,/用户发送缓冲区指针int len,/待发送缓冲区的字节数int flags/一般设置为0表示正常发送数据);,在已经建立连接的套接字上发送数据。,调用成功:返回发送的字节数。调用失败:返回SOCKET_ERROR,缺省是阻塞函数,在执行send时,若套接字的系统缓冲区没有可用空间,则程序一直阻塞等待,直到有可用的空间为止。,#define BUFFERLEN 1024/数据缓冲区大小 char sendBufBUFFERLEN;/用户发送缓冲区sprintf(sendBuf,“欢迎访问我的服务!”);/strlen(buffer)+1 把字符串的结束符0一块发送int sendlen=send(SockConn,sendBuf,strlen(sendBuf)+1,0);,在已建立连接的套接字上接收数据recv(),函数形式:int recv(SOCKET s,char*buf,/用户接收缓冲区指针 int len,/接收缓冲区的字节数 int flags/一般设置为0表示正常接收数据);,对于已经建立连接的套接字,recv可以从该套接字接收数据。,调用成功:返回接收的字节数。调用失败:返回SOCKET_ERROR,缺省是阻塞函数,在执行recv时,若套接字的系统缓冲区没有数据可取,则程序一直阻塞等待,直到接收到对方数据为止。,#define BUFFERLEN 1024/数据缓冲区大小 char recvBufBUFFERLEN;/用户接收缓冲区int recvlen=recv(sockClient,recvBuf,sizeof(recvBuf),0);,在无连接的套接字上发送数据sendto(),函数形式:int sendto(SOCKET s,const char*buf,/用户发送缓冲区指针int len,/待发送缓冲区的字节数int flags,/一般设置为0 const struct sockaddr*to,/发送数据的目的地址int tolen/目的地址长度);,对于无连接的套接字,使用sendto()从套接字上发送数据。,调用成功:返回发送的字节数。调用失败:返回SOCKET_ERROR,缺省是阻塞函数,在执行sendto时,如果套接字的系统缓冲区没有可用空间,则程序一直阻塞等待,直到有可用的空间为止。,#define BUFFERLEN 1024/数据缓冲区大小 char sendBufBUFFERLEN;/用户发送缓冲区sprintf(sendBuf,我是张三!);sendlen=sendto(sockClient,sendBuf,strlen(sendBuf)+1,0,(sockaddr*),在无连接的套接字上接收数据recvfrom(),函数形式:int recvfrom(SOCKET s,char*buf,/用户接收缓冲区指针 int len,/接收缓冲区的字节数 int flags,/一般设置为0 struct sockaddr*from,/指明接收数据的来源地址 int*fromlen/来源地址长度);,对于无连接的套接字,使用recvfrom()从套接字上接收一个数据报并保存发送源地址。,调用成功:返回接收的字节数。调用失败:返回SOCKET_ERROR,缺省是阻塞函数,在执行recvfrom时,若套接字的系统缓冲区没有数据可取,则程序一直阻塞等待,直到接收到对方数据为止。,#define BUFFERLEN 1024/数据缓冲区大小 char recvBufBUFFERLEN;/用户接收缓冲区SOCKADDR_IN addrClient;/来自客户的地址int addrlen=sizeof(addrClient);/初始化客户地址长度参数/从客户端接收recvlen=recvfrom(sockSrv,recvBuf,sizeof(recvBuf),0,(sockaddr*),4.数据报套接字编程,用户数据报协议 UDP-特点,UDP 是无连接的,即发送数据之前不需要建立连接(当然发送数据结束时也没有连接可释放),因此减少了开销和发送数据之前的时延。UDP 使用尽最大努力交付,即不保证可靠交付,同时也不使用拥塞控制,因此主机不需要维持具有许多参数的、复杂的连接状态表。UDP用户数据报只有 8 个字节的首部开销,比 TCP 的 20 个字节的首部要短。,由于 UDP 没有拥塞控制,因此网络出现的拥塞不会使源主机的发送速率降低。这对某些实时应用是很重要的。UDP 是面向报文的。这就是说,UDP 对应用程序交下来的报文不再划分为若干个分组来发送,也不把收到的若干个报文合并后再交付给应用程序。应用程序交给 UDP 一个报文,UDP 就发送这个报文;而 UDP 收到一个报文,就把它交付给应用程序。应用程序必须选择合适大小的报文。,用户数据报协议 UDP-特点,伪首部,源端口,目的端口,长 度,检验和,数 据,首 部,UDP长度,源 IP 地址,目的 IP 地址,0,17,IP 数据报,字节,4,4,1,1,2,12,2,2,2,2,字节,发送在前,数 据,首 部,UDP 用户数据报,UDP 用户数据报的首部格式,伪首部,源端口,目的端口,长 度,检验和,数 据,首 部,UDP长度,源 IP 地址,目的 IP 地址,0,17,IP 数据报,字节,4,4,1,1,2,12,2,2,2,2,字节,发送在前,数 据,首 部,UDP 用户数据报,在计算检验和时,临时把“伪首部”和 UDP 用户数据报连接在一起。伪首部仅仅是为了计算检验和。,数据报套接字,在TCP/IP协议组中,使用UDP协议来实现数据报套接字。,它定义了一种无连接、不可靠的双向数据传输服务。,TCP/IP协议架构,TCP,UDP,传输层,应用层,网络层,网络接口层,IP地址定义了某个主机。端口号标识了在此特定主机上的多个进程中的一个。,UDP,UDP,Internet,数据报套接字通信,bind(),recvfrom(),sendto(),closesocket(),socket(),sendto(),recefrom(),closesocket(),socket(),服务器,客户端,创建一个新的套接字,创建一个新的套接字,将一个本地地址关联到一个套接字上,阻塞等待接收客户数据,发送数据,发送数据,接收数据,关闭套接字,关闭套接字,数据报套接字工作流程(服务器先启动),使用socket()函数创建套接字。使用bind()函数将创建的套接字与本地地址绑定。收发数据(sendto()函数recvfrom()函数)使用closesocket()关闭套接字,客户端工作流程:使用socket()函数创建套接字。收发数据(sendto()函数recefrom()函数)使用closesocket()关闭套接字,服务器端工作流程:,数据报套接字工作流程总结,/UdpServer.cpp#include/标准输入输出系统文件#include/winsock声明文件#pragma comment(lib,WS2_32)/链接到WS2_32.lib#define PORT 6000/此服务器监听的端口号#define BUFFERLEN 1024/数据缓冲区大小void main()/通过载入Winsock库,来使用Winsock的相关函数 WSADATA wsaData;/用来返回Winsock库的详细信息 WORD version=MAKEWORD(2,2);int ret=WSAStartup(version,服务器程序:,数据报套接字程序的编写,WSADATA,SOCKET sockSrv;/服务器端套接字/socket(协议地址族,套接字类型,协议);sockSrv=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);if(INVALID_SOCKET=sockSrv)printf(创建套接字失败:%dn,WSAGetLastError();return;sockaddr_in addrSrv;/设置服务器端地址 addrSrv.sin_family=AF_INET;/Internet协议地址组 addrSrv.sin_port=htons(PORT);/把16位端口转换为网络字节=inet_addr(“192.168.0.1);/指定IP/绑定套接字到服务器端地址/bind(服务器端套接字,服务器端地址,服务器端地址长度);if(SOCKET_ERROR=bind(sockSrv,(sockaddr*),sockaddr_in,SOCKADDR_IN addrClient;/声明来自客户的地址,以备填充 int addrlen=sizeof(addrClient);/初始化客户地址长度参数 char sendBufBUFFERLEN;/用户发送缓冲区 char recvBufBUFFERLEN;/用户接收缓冲区 int sendlen;int recvlen;/发送接收数据长度 while(true)/进入循环/从客户端接收/recvfrom(服务器端套接字,接收缓冲区,缓冲区大小,接收标志,接收数据的来源地址,来源地址长度)recvlen=recvfrom(sockSrv,recvBuf,sizeof(recvBuf),0,(sockaddr*),/向客户端发送sprintf(sendBuf,“欢迎IP%s;端口%d访问我的服务!”,inet_ntoa(addrClient.sin_addr),ntohs(addrClient.sin_port);/sendto(服务器端套接字,发送缓冲区,发送数据长度,发送标志,发送数据的目的地址,目的地址长度);/strlen(sendBuf)+1 指把结束符0一块发送 sendlen=sendto(sockSrv,sendBuf,strlen(sendBuf)+1,0,(sockaddr*)/释放Winsock库,/UdpClient.cpp#include/标准输入输出系统文件#include/winsock声明文件#pragma comment(lib,WS2_32)/链接到WS2_32.lib#define PORT 6000/此服务器监听的端口号#define BUFFERLEN 1024/数据缓冲区大小void main()/通过载入Winsock库,来使用Winsock的相关函数WSADATA wsaData;/用来返回Winsock库的详细信息 WORD version=MAKEWORD(2,2);int ret=WSAStartup(version,客户端程序:,SOCKET sockClient;/声明客户端套接字/socket(协议地址族,套接字类型,协议);sockClient=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);if(INVALID_SOCKET=sockClient)printf(创建套接字失败:%dn,WSAGetLastError();return;/填写远程地址信息sockaddr_in addrSrv;/声明服务器端地址addrSrv.sin_family=AF_INET;addrSrv.sin_port=htons(PORT);/把16位端口号转换为网络字节=inet_addr(192.168.0.1);int addrlen=sizeof(addrSrv);/初始化服务器端地址长度参数,char sendBufBUFFERLEN;/用户发送缓冲区char recvBufBUFFERLEN;/用户接收缓冲区int sendlen;/发送数据长度int recvlen;/接收数据长度/向服务器发送数据sprintf(sendBuf,I am a client!);/sendto(客户端套接字,发送缓冲区,发送数据长度,发送标志,发送数据的目的地址,目的地址长度);sendlen=sendto(sockClient,sendBuf,strlen(sendBuf)+1,0,(sockaddr*),/从服务器读取数据/recvfrom(客户端套接字,接收缓冲区,缓冲区大小,接收标志,接收数据的来源地址,来源地址长度)recvlen=recvfrom(sockClient,recvBuf,sizeof(recvBuf),0,(sockaddr*)/释放Winsock库,UDP服务器正在运行.,服务器端程序运行结果:,发送到服务器的数据:I am a client!;字节数:15接收到服务器端的数据:欢迎IP127.0.0.1;端口1066访问我的服务!;字节数:42,客户端程序运行结果:,发送到服务器的数据:I am a client!;字节数:15接收到服务器端的数据:欢迎IP127.0.0.1;端口1067访问我的服务!;字节数:42,客户端的 IP:127.0.0.1,端口:1066接收到客户端的数据:I am a client!;字节数:15发送到客户端的数据:欢迎IP127.0.0.1;端口1066访问我的服务!;字节数:42,客户端的 IP:127.0.0.1,端口:1067接收到客户端的数据:I am a client!;字节数:15发送到客户端的数据:欢迎IP127.0.0.1;端口1067访问我的服务!;字节数:42,5.流式套接字编程,TCP 报文段的首部格式,TCP 报文段分为首部和数据两部分。TCP 的全部功能都体现在它首部中各字段的作用。TCP 报文段首部的前 20个 字节是固定的,后面有 4N 字节是根据需要而增加的选项(N 必须是整数)。因此 TCP 首部的最小长度是 20 字节。,TCP首部,20 字节的固定首部,目 的 端 口,数据偏移,检 验 和,选 项(长 度 可 变),源 端 口,序 号,紧 急 指 针,窗 口,确 认 号,保 留,FIN,32 位,SYN,RST,PSH,ACK,URG,位 0 8 16 24 31,填 充,TCP 数据部分,TCP 首部,TCP 报文段,IP 数据部分,IP 首部,发送在前,TCP首部,20字节固定首部,目 的 端 口,数据偏移,检 验 和,选 项(长 度 可 变),源 端 口,序 号,紧 急 指 针,窗 口,确 认 号,保 留,FIN,SYN,RST,PSH,ACK,URG,位 0 8 16 24 31,填 充,源端口和目的端口字段各占 2 字节。端口是运输层与应用层的服务接口。,TCP首部,20字节固定首部,目 的 端 口,数据偏移,检 验 和,选 项(长 度 可 变),源 端 口,序 号,紧 急 指 针,窗 口,确 认 号,保 留,FIN,SYN,RST,PSH,ACK,URG,位 0 8 16 24 31,填 充,序号字段占 4 字节。TCP 连接中传送的数据流中的每一个字节都编上一个序号。序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。,TCP首部,20字节固定首部,目 的 端 口,数据偏移,检 验 和,