《【教学课件】第5章网络软件开发技术-编程篇.ppt》由会员分享,可在线阅读,更多相关《【教学课件】第5章网络软件开发技术-编程篇.ppt(71页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第第5章章 网络软件开发技术网络软件开发技术 编程篇编程篇TCP socket&UDP socket西安交通大学西安交通大学西安交通大学西安交通大学计算机教学实验中心计算机教学实验中心计算机教学实验中心计算机教学实验中心软件开发技术基础软件开发技术基础软件开发技术基础软件开发技术基础1/9/20231Socket 简介简介lSocket是是TCP/IP协议族提供的协议族提供的应用编程接口应用编程接口。l应用层的应用系统通过调用应用层的应用系统通过调用Socket的接口来的接口来利用传输层提供的各种服务,包括可靠的流协利用传输层提供的各种服务,包括可靠的流协议议TCP和不可靠的数据报协议和不可靠
2、的数据报协议UDP。应用程序传输层协议Socket1/9/20232Socket 简介简介l1982-Berkeley Software Distributions 操操作系统引入了作系统引入了sockets 作为本地进程之间通信作为本地进程之间通信的接口的接口l1986-Berkeley 扩展了扩展了socket 接口使之支持接口使之支持UNIX 下的下的TCP/IP 通信通信l现在很多应用现在很多应用(FTP,Telnet,etc)都依赖这一都依赖这一接口接口1/9/20233Socket 简介简介lSocketl是一个编程接口是一个编程接口l是一种特殊的文件描述符是一种特殊的文件描述符(
3、everything in Unix is a file)l并不仅限于并不仅限于TCP/IPl通信协议通信协议l面向连接面向连接(Transmission Control Protocol-TCP/IP)l无连接无连接(User Datagram Protocol-UDP 和和 Inter-network Packet Exchange-IPX)1/9/20234WinSockl从从 Berkeley Sockets(Unix)移植移植l包括了许多对包括了许多对windows环境的扩展支持环境的扩展支持l开放的网络编程接口开放的网络编程接口lAPI 开放开放l多个厂商提供多个厂商提供 wins
4、ockl源码和二进制兼容性源码和二进制兼容性l最初的最初的Winsock版本是版本是1.1版,在它的基础版,在它的基础上,微软又进一步提供了上,微软又进一步提供了Winsock2接口。接口。Winsock2支持多种底层的网络协议,如支持多种底层的网络协议,如TCP/IP、ATM、IPX等等1/9/20235WinSock.dllFTP WinSock.dllTCP/IPIPXAppleTalkNetBIOSRemote Access Service(RAS)FTP TCP/IPModemNetwork DriversLANApplicationWindows Socket,协议协议 和应用和应
5、用Phone Line1/9/20236Berkeley socket 和和 WinSock的的不同不同lBerkeley Socket 是一个是一个 int 数据类型数据类型,WinSock Socket 则是则是 SOCKET 数据类型数据类型lWinSock 中以中以SOCKET_ERROR 代表出错代表出错,Berkeley Socket 以以-1 代表出错代表出错lWinSock 应用必须首先调用应用必须首先调用 WSAStartup()初始初始化化,并在结束前调用并在结束前调用 WSACleanup()释放资源释放资源1/9/20237void main(void)/*The fo
6、llowing two lines needed for Windows socket*/WORD wVersionRequested=MAKEWORD(2,2);/*WSA 函数的参数 */WSADATA wsaData;/*WSA 函数的参数 */*初始化 winsock */WSAStartup(wVersionRequested,&wsaData);/*Create a socket*/My_SocketID=socket(.);Step 1:指定需要使用的Winsock规范的最高版本Step 2:初始化 Winsock,装入Winsock.dllStep 3:开始使用Winsock
7、version 2.21.初始化初始化 Winsock1/9/20238Winsock DLLl在装入在装入Winsock DLL 之前调用任何之前调用任何 Winsock 函数都会函数都会返回返回 SOCKET-ERROR 错误错误.l调用调用 WSAStartup装入装入Winsock DLL int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData );lwVersionRequested Winsock DLL versionlX(high order):sub-versionlY(low order):main version
8、lMAKEWORD(X,Y)宏定义将两个字节组装成一个宏定义将两个字节组装成一个WORD1/9/20239在在调用用“closesocket”函数之后函数之后程序程序结结束之前束之前,释放释放socket资源资源/*This stuff cleans-up winsock*/WSACleanup();Clear winsocktypedef struct WSAData WORD wVersion;WORD wHighVersion;char szD escriptionWSADESCRIPTION_LEN+1;char szSystemStatusWSASYS_STATUS_LEN+1;un
9、signed short iMaxSockets;unsigned short iMaxUdpDg;char FAR*lpVendorInfo;WSADATA,*LPWSADATA;1/9/2023102.创建创建socket-socket()函数函数SOCKET socket_id=socket(AF_INET,SOCK_STREAM,0);“AF_INET”=使用 IP 协议“SOCK_STREAM”=使用 TCPReturns socket ID on successReturn INVALID_SOCKET on error总是 01/9/202311Basic Socket Call
10、s(socket)/Berkeley 形式形式int socket(int family int type,int protocol);/WinSock 形式形式SOCKET socket(int family,int type,int protocol);1/9/202312socket(续续)lSOCKET socket(int family,int type,int protocol);lfamily 是地址族是地址族lAF_INET /internet 协议协议lAF_UNIX/unix internal协议协议lAF_NS /Xerox NS协议协议lAF_IMPLINK /Inte
11、rface Message协议协议ltypelSOCK_STREAM /流式流式 socketlSOCK_DGRAM /数据报数据报 socketlSOCK_RAW /raw socketlprotocol 参数通常置为参数通常置为01/9/2023133.TCP Sockets 编程编程l创建一个被动模式创建一个被动模式(server,服务器,服务器)的的 socket.l建立应用层的连接建立应用层的连接lClient/Server 交互交互l在发送和接收数据之前在发送和接收数据之前lclient 必须调用必须调用 connect 连接服务器连接服务器lserver 必须调用必须调用 acc
12、ept 接收接收client的连接的连接l发送和接收数据发送和接收数据.l关闭连接关闭连接.1/9/202314TCP Sockets 编程基本流程编程基本流程socket()bind()listen()accept()send()recv()close()send()socket()recv()close()connect()recv()建立连接数据请求数据响应断连指示ClientServer1/9/202315socket()bind()listen()accept()close()调用调用socket创建一个套接字,并在传输层创建一个套接字,并在传输层实体中分配表空间,返回一个文件描述符
13、,实体中分配表空间,返回一个文件描述符,用于以后调用中使用。用于以后调用中使用。调用调用bind将某地址赋予,使得远程应用程序将某地址赋予,使得远程应用程序能访问本地应用程序。能访问本地应用程序。调用调用listen分配数据空间,以便存分配数据空间,以便存储多个用户的连接建立请求。储多个用户的连接建立请求。调用调用accept将本地应用程序阻塞起来,将本地应用程序阻塞起来,等待接收客户端发来的连接请求。等待接收客户端发来的连接请求。释放连接:使用释放连接:使用close原语单独释放连接。原语单独释放连接。服务器端1/9/202316socket()close()connect()调用调用soc
14、ket创建一个套接字,并在传输创建一个套接字,并在传输层实体中分配表空间,返回一个文件描层实体中分配表空间,返回一个文件描述符,用于以后调用中使用。述符,用于以后调用中使用。调用调用connect阻塞应用程序,传输层实阻塞应用程序,传输层实体开始建立连接,当连接建立完成时,体开始建立连接,当连接建立完成时,取消阻塞。取消阻塞。释放连接:使用释放连接:使用close原语单独释放连接。原语单独释放连接。客户端1/9/202317send()send()recv()recv()数据请求数据响应双方使用双方使用send和和receive完成完成数据的全双工发送数据的全双工发送数据传输1/9/20231
15、8int status=bind(socket_id,(struct sockaddr_in*)my_addr,sizeof(my_addr);sockaddr_in 结构,描述本机的端口和 IP 地址Sockaddr_in 结构的字节长度Return code(SOCKET_ERROR if error)Socket ID returned by socket functionBind()函数函数1/9/202319Basic Socket Calls(bind)/Berkeley 形式形式int bind(int sockfd,struct sockaddr*addr,int addrLe
16、n);/WinSock 形式形式int bind(SOCKET sockfd,struct sockaddr*addr,int addrLen);1/9/202320bind(续续)int bind(SOCKET sockfd,struct sockaddr*addr,int addrLen);lsockfd 由由socket()调用返回调用返回laddr 是指向是指向 sockaddr_in 结构的指针,包含结构的指针,包含server IP 地址和端口号地址和端口号lstruct sockaddr_inshort sin_family/address familyu_short sin_p
17、ort /port numberstruct in_addr sin_addr /IP address(32-bits)laddrLen-sizeof(struct sockaddr_in)1/9/202321struct sockaddr_in my_addr;/*My(client)Internet address */*Set My(clients)IP Address-*/my_addr.sin_family =AF_INET;/*Address Family To Be Used*/my_addr.sin_port =htons(6666);/*Port number to use*
18、/my_addr.sin_addr.s_addr=htonl(INADDR_ANY);/*My IP address*/Step 1:初始化该数据结构Step 2:填充信息The“sock_addr”structure1/9/202322地址结构地址结构l通用地址结构通用地址结构lstruct sockaddr u_short sa_family;/地址族地址族,AF_xxx char sa_data14;/14字节协议地址字节协议地址 ;lInternet协议地址结构协议地址结构lstruct sockaddr_in short sin_family;/地址族地址族,AF_INET,2 by
19、tes u_short sin_port;/端口,端口,2 bytes struct in_addr sin_addr;/IPV4地址,地址,4 bytes char sin_zero8;/8 bytes unused ;lIPv4地址结构地址结构lstruct in_addr /internet address u_long s_addr;/socket address ;1/9/202323lunsigned long inet_addr(char *address);laddress是以是以NULL结尾的点分结尾的点分IPv4字符串。该函数返字符串。该函数返回回32位的地址,如果位的地址
20、,如果cp字符串包含的不是合法的字符串包含的不是合法的IP地地址,则函数返回。例:址,则函数返回。例:lin_addr addr;laddr.s_addr=inet_addr(202.117.50.26);lchar*inet_ntoa(struct in_addr address)laddress是是IPv4地址结构,函数返回一指向包含点分地址结构,函数返回一指向包含点分IP地址的静态存储区字符指针,如果错误则函数返回地址的静态存储区字符指针,如果错误则函数返回NULL地址转换函数地址转换函数(inet_addr()和和 inet_ntoa()1/9/202324gethostname()l
21、得到本机的名称得到本机的名称lint gethostname(char*hostname,int bufferLength)lhostname是一个字符数组,是一个字符数组,bufferLength是该是该数组的长度。当调用成功,函数返回数组的长度。当调用成功,函数返回0并且将本机的并且将本机的名称赋值给名称赋值给hostname;当调用失败,则返回;当调用失败,则返回SOCKET_ERROR.1/9/202325从域名解析得到从域名解析得到IP地址地址(gethostbyname)lgethostbyname():给定主机名,给定主机名,(例如例如 ),得到主机得到主机IP地址地址.lstr
22、uct hostent*getbyhostname(char*hostname)lchar*h_name;/official name of hostlchar*h_aliases;/alias listlshort h_addrtype;/address family(e.g.,AF_INET)lshort h_length;/length of address(4 for AF_INET)lchar*h_addr_list;/list of addresses(null pointer terminated)1/9/202326l下面的代码完成对下面的代码完成对的域名解析,得到其的域名解析
23、,得到其IP地址:地址:lhostent*phostent;/指向指向hostent结构的指针结构的指针lin_addr in;/IPV4地址结构地址结构lif(phostent=gethostbyname()=NULL)l printf(gethostbyname()错误:错误:%d,WSAGetLastError();lelsell /拷贝拷贝4字节的字节的IP地址到地址到IPV4地址结构地址结构l memcpy(&in,phostent-h_addr,4);l printf(主机主机%s 的的IP地址是:地址是:,phostent-h_name);l printf(%s,inet_nto
24、a(in);l域名解析示例域名解析示例1/9/202327字节序字节序l不同的计算机系统采用不同的字节序存储数据,不同的计算机系统采用不同的字节序存储数据,同样一个两字节的同样一个两字节的16位整数位整数(0X0304),在内存,在内存中存储的方式就不同:中存储的方式就不同:l一种方式是将低字节存储在起始地址,称为一种方式是将低字节存储在起始地址,称为“Little-Endian”字节序,字节序,Intel、AMD等采用的是等采用的是这种方式;这种方式;l另一种是将高字节存储在起始地址,称为另一种是将高字节存储在起始地址,称为“Big-Endian”字节序,由字节序,由Macintosh、Mo
25、torola等所采用等所采用03040403内存地址增大方向Little-EndianBig-Endian1/9/202328字节序转换函数字节序转换函数l把给定系统所采用的字节序称为把给定系统所采用的字节序称为主机字节序主机字节序。为了。为了避免不同类别主机之间在数据交换时由于对于字节避免不同类别主机之间在数据交换时由于对于字节序解释的不同而导致的差错,引入了序解释的不同而导致的差错,引入了网络字节序网络字节序,即网络传输所采用的字节序。规定网络字节序使用即网络传输所采用的字节序。规定网络字节序使用“Big-Endian”方式。方式。l主机到网络主机到网络lu_long htonl(u_lo
26、ng hostlong);lu_short htons(u_short short);l网络到主机网络到主机lu_long ntohl(u_long hostlong);lu_short ntohs(u_short short);1/9/202329例子例子:创建创建 SOCKADDR_IN下面的代码演示了如何利用上面描述的下面的代码演示了如何利用上面描述的inet_addr 和和htons 函数来创建函数来创建sockaddr_in结构。结构。sockaddr_in internetAddr;int port=6666;internetAddr.sin_family=AF_INET;/将点分
27、的将点分的IP地址转换为地址转换为4字节的整数并赋值给字节的整数并赋值给s_addr域域internetAddr.sin_addr.s_addr=inet_addr(202.117.50.26);/port变量以变量以主机字节序主机字节序存储,因此将它转换为网络字节序并赋值给存储,因此将它转换为网络字节序并赋值给sin_portinternetAddr.sin_port=htons(port);1/9/202330int status=listen(socket_id,3);指定了正在等待连接的最大队列长度Return code(SOCKET_ERROR if error)Socket ID
28、returnedby socket functionlisten()函数函数1/9/202331Basic Socket Calls(listen)/Berkeley formint listen(int sockfd,int backlog);/WinSock formint listen(SOCKET sockfd,int backlog);1/9/202332listen(续续)int listen(SOCKET sockfd,int backlog);lsockfd监听连接的监听连接的Socketl1=backlog 0)ret=recv(s,p,left,0);if(ret=SOCK
29、ET_ERROR)/错误处理 left-=ret;p+=ret;1/9/202347int status=send(socket_id,out_buffer,MAX_BUFFER_SIZE,0);Return code(SOCKET_ERROR if error)Socket ID returnedby socket functionAlways 0The maximum buffer size待发送数据缓冲区的指针On success,the number of bytes actually sentsend()函数函数1/9/202348Basic Socket Calls(send)/B
30、erkeley formint send(int s const char*bytesToSend,int nBytes,int flags);/WinSock form int send(SOCKET s,const char*bytesToSend,int nBytes,int flags);1/9/202349send(续续)int send(SOCKET s,const char*bytesToSend,int nBytes,int flags);ls是已经连接的是已经连接的SocketlbytesToSend指向待发送数据缓冲区的指针指向待发送数据缓冲区的指针lnBytes是待发送数
31、据的字节数是待发送数据的字节数lflags le.g.,MSG_OOBl注意:send()并不保证发送所有请求的数据。它实际并不保证发送所有请求的数据。它实际发送的字节数由返回值指示。也许需要循环调用发送的字节数由返回值指示。也许需要循环调用send()来得到需要的结果。来得到需要的结果。1/9/202350Example#define BUFSIZE 4096char bufBUFSIZE;int left=BUFSIZE;char*p=buf;/给给buf填充填充4096字节的待发数据字节的待发数据./这里假设这里假设s是已经连接的流式是已经连接的流式Socket.while(left 0
32、)ret=send(s,p,left,0);if(ret=SOCKET_ERROR)/错误处理错误处理 left-=ret;p+=ret;1/9/202351int status=closesocket(socket_id);Return code(SOCKET_ERROR if error)Socket ID returnedby socket functionclosesocket()函数函数1/9/202352Examplel使用流式使用流式Socket的简单应用,它包括客户和的简单应用,它包括客户和服务器两个程序。当客户同服务器建立连接后,服务器两个程序。当客户同服务器建立连接后,客户
33、会向服务器发送一个客户会向服务器发送一个“来自客户的消息来自客户的消息”这样的字符串,服务器以发送回字符串这样的字符串,服务器以发送回字符串“欢迎欢迎连接服务器连接服务器”作为响应。然后,双方程序都结作为响应。然后,双方程序都结束运行。束运行。1/9/202353UDP Sockets 编程编程l数据报服务数据报服务(UDP):不可靠的传送不可靠的传送l创建创建 UDP socketslClientlServerl发送数据发送数据.l接收数据接收数据.1/9/202354无连接协议的无连接协议的Socket 调用调用lclient/server UDP 通信通信ServerSocket()Bi
34、nd()Recvfrom()Blocks until data receivedProcess requestSendto()ClientSocket()Bind()Sendto()Blocks until data receivedProcess replyrecvfrom()1/9/202355无连接协议的无连接协议的Socket 调用调用l注意注意 recvfrom()仅仅监听仅仅监听 socket 地址地址ServerSocket()Bind()Recvfrom()Blocks until data receivedProcess requestSendto()ClientSocket
35、()Bind()Sendto()Blocks until data receivedProcess replyrecvfrom()1/9/202356无连接协议的无连接协议的Socket 调用调用l所以任何发送到该所以任何发送到该socket的数据包都会得到响应的数据包都会得到响应ServerSocket()Bind()Recvfrom()Blocks until data receivedProcess requestSendto()ClientSocket()Bind()Sendto()Blocks until data receivedProcess replyrecvfrom()1/9
36、/202357无连接协议的无连接协议的Socket 调用调用l对于对于server,这是需要的,但是这是需要的,但是client如何呢如何呢?ServerSocket()Bind()Recvfrom()Blocks until data receivedProcess requestSendto()ClientSocket()Bind()Sendto()Blocks until data receivedProcess replyrecvfrom()1/9/202358无连接协议的无连接协议的Socket 调用调用l如果一个如果一个“伪造伪造”的应答到达的应答到达client的的 socket
37、,它就如同它就如同server发送的一样被处理发送的一样被处理ServerSocket()Bind()Recvfrom()Blocks until data receivedProcess requestSendto()ClientSocket()Bind()Sendto()Blocks until data receivedProcess replyrecvfrom()1/9/202359无连接协议的无连接协议的Socket 调用调用l换句话说,换句话说,client 等待数据等待数据,但是并不检测谁发送的数据但是并不检测谁发送的数据ServerSocket()Bind()Recvfrom(
38、)Blocks until data receivedProcess requestSendto()ClientSocket()Bind()Sendto()Blocks until data receivedProcess replyrecvfrom()close()close()1/9/202360创建创建 UDP socketSOCKET socket(int family,int type,int protocol);SOCKET sock;sock=socket(AF_INET,SOCK_DGRAM,0);1/9/202361服务器绑定地址服务器绑定地址SOCKET s;sockadd
39、r_in addr;s=socket(AF_INET,SOCK_DGRAM,0);addr.sin_family=AF_INET;addr.sin_port=htons(2000);addr.sin_addr.s_addr=htonl(INADDR_ANY);if(bind(s,(sockaddr*)&addr,sizeof(addr)=SOCKET_ERROR)/错误处理Fill in addr1/9/202362Basic Socket Calls(sendto)/Berkeley formint sendto(int s,const char*bytesToSend,int nBytes
40、,int flags,struct sockaddr*to,int sizeOfSockaddr);/WinSock form int sendto(SOCKET s,const char*bytesToSend,int nBytes,int flags,struct sockaddr*to,int sizeOfSockaddr);1/9/202363sendto(续续)int sendto(SOCKET s,const char*bytesToSend,int nBytes,int flags struct sockaddr*to,int sizeOfSockaddr);lto是指向接收者地
41、址的指针是指向接收者地址的指针lsizeOfSockaddr is sizeof(struct sockaddr_in)l当调用成功,当调用成功,sendto()返回被成功发送的字节数;返回被成功发送的字节数;当调用失败,返回当调用失败,返回SOCKET_ERRORl需要说明两点:第一,由于需要说明两点:第一,由于UDP是不可靠的,因此是不可靠的,因此sendto()的成的成功返回并不代表数据被成功发送到了目标地址,甚至不能保证数功返回并不代表数据被成功发送到了目标地址,甚至不能保证数据已经从本机发出;第二,据已经从本机发出;第二,sendto()一次发送的数据大小是有上一次发送的数据大小是有
42、上限的,至少要小于限的,至少要小于IP包的大小(约包的大小(约64K)1/9/202364sendto()的典型用法的典型用法l#define BUFSIZE 1024lSOCKET s;lsockaddr_in addr;lchar bufBUFSIZE;lint bufLen;lint bytesSent;ls=socket(AF_INET,SOCK_DGRAM,0);lif(s=INVALID_SOCKET)ll/错误处理错误处理.llelsell/填充接收者的地址填充接收者的地址laddr.sin_family=AF_INET;laddr.sin_port=htons(2000);la
43、ddr.sin_addr.s_addr=inet_addr(202.117.50.26);l/填充待发数据到缓冲区填充待发数据到缓冲区lstrcpy(buf,Hello,World!);lbufLen=strlen(buf);l/发送数据报发送数据报lbytesSent=sendto(s,buf,bufLen,0,(sockaddr*)&addr,sizeof(addr);lif(bytesSent=SOCKET_ERROR)ll/错误处理错误处理.llclosesocket(s);l1/9/202365Basic Socket Calls(recvfrom)/Berkeley formint
44、 recvfrom(int s char*receivedData,int nBytes,int flags,struct sockaddr*from,int sizeOfSockaddr);/WinSock form int recvfrom(SOCKET s,char*receivedBytes,int nBytes,int flags,struct sockaddr*from,int sizeOfSockaddr);1/9/202366recvfrom(续续)int recvfrom(SOCKET s,char*receivedData,int nBytes,int flags stru
45、ct sockaddr*from,int sizeOfSockaddr);lfrom是指向数据发送者地址的指针是指向数据发送者地址的指针lsizeOfSockaddr is sizeof(struct sockaddr_in)l当调用成功,当调用成功,recvfrom()返回被接收的字节数;当返回被接收的字节数;当调用失败,返回调用失败,返回SOCKET_ERROR。这里要说明,。这里要说明,如果返回如果返回0,并不说明有错误发生,而是读取了一个,并不说明有错误发生,而是读取了一个纯纯UDP报文头。如果接收到的数据报比提供的接收报文头。如果接收到的数据报比提供的接收缓冲区要大,那么会出现缓冲区
46、要大,那么会出现WSAEMSGSIZE错误。错误。1/9/202367recvfrom()的典型用法的典型用法l#define BUFSIZE 1024lSOCKET s;lsockaddr_in addr;/本本Socket地址结构地址结构lchar bufBUFSIZE;/接收缓冲区接收缓冲区lint bytesRecv;lsockaddr_in addrFrom;/发送方地址结构发送方地址结构lint addrFromLen=sizeof(addrFrom);/发送方地址结构长度发送方地址结构长度lin_addr inFrom;/发送方发送方IP地址地址ls=socket(AF_INET
47、,SOCK_DGRAM,0);lif(s=INVALID_SOCKET)ll/错误处理错误处理.l1/9/202368lelsell/填充本地地址结构以便从中读取数据填充本地地址结构以便从中读取数据laddr.sin_family=AF_INET;laddr.sin_port=htons(2000);laddr.sin_addr.s_addr=htonl(INADDR_ANY);l/绑定绑定socketlif(bind(s,(sockaddr*)&addr,sizeof(addr)=SOCKET_ERROR)ll/错误处理错误处理.llelsellbytesRecv=recvfrom(s,bu
48、f,BUFSIZE,0,l(sockaddr*)&addrFrom,&addrFromLen);lif(bytesRecv=SOCKET_ERROR)ll/错误处理错误处理.llelsell/处理得到的数据处理得到的数据.lmemcpy(&inFrom,&addrFrom.sin_addr.s_addr,4);lprintf(服务器从地址服务器从地址%s,端口为端口为%d 的客户端收到字节数的客户端收到字节数%d,linet_ntoa(inFrom),ntohs(addrFrom.sin_port),bytesRecv);lllclosesocket(s);l1/9/202369Examplel使用的是无连接的数据报使用的是无连接的数据报socket完成和的示例完成和的示例同样的功能。同样的功能。1/9/202370实例实例 从从Web服务器下载文件服务器下载文件MethodURLCRLFSpCRLF简单HTTP请求的格式 1/9/202371