1、1/19TCP/IP 通信程序设计1、实验目的初步掌握 C 语言 TCP/IP 通信程序的设计。2、实验环境1、Windows 2000/NT/XP 操作系统。2、TCP/IP 协议。3、编程工具:Microsoft Visual C+ 2005。3、相关知识3.1 TCP/IP 协议族表 1 TCP/IP 协议族应用层(FTP, DNS, HTTP, TELNET, SMTP 等)TCP UDPICMP IGMPIPARP RARP网络接口层TCP 具有以下特点:1、面向连接 。端到端的 TCP 连接会关注连接的状态,而网络的中间路由器只关心 IP 分组的转发。2、可靠数据传递 。TCP 使
2、用顺序号、采用直接应答方式,并在必要时通过重传来保证发自源端的数据能成功地被传递到目的地。3、流量控制 。接收方向发送方发送一个接收窗口值,告诉发送方接收方能够处理多少数据。在收到接收方发来的应答前,TCP 发送方最多只能发送等于该窗口值的数据量。4、拥塞控制 。用于防止 TCP 发送方发送的信息量超过网络中链路或路由器的最大处理能力。流量控制和拥塞控制结合起来,使得 TCP 主机能迅速而公平地调整其发送速2/19率,以达到与网络及接收方的处理能力相匹配。3.2 端口与 Socket在进程通信的意义上,网络通信的最终地址不仅网络层提供的 IP 地址,还应包括描述进程的协议端口(protocol
3、 port) 。若没有端口,传输层就无法知道数据应当交付给应用层的哪个进程。因此,端口标示了应用层的进程。TCP 和 UDP 分别提供了 216 个不同的端口值。端口分为两类:1、周知端口(well-know port) ,其值为 0-1023,由 ICANN 负责分配(见RFC 1700) 。其中 TCP 和 UDP 均规定小于 256 的端口作为保留端口。2、临时端口,也称本地分配。进程需要访问传输服务时,向本地操作系统提出动态申请,操作系统返回一个本地唯一的端口号,进程通过合适的系统调用将自己和相应的端口号联系起来。Socket 由 4BSD UNIX 首先提出,目的是解决网络通信问题。
4、Socket 的英文原义是“插座” ,Socket 与电话交换机的插座非常类似,进程通信前,双方各创建一个端点,每一个 Socket 有一个本地唯一的 Socket 号,由操作系统分配。Socket 与IP 地址、 IP 地址的关系如图 1 所示。2 0 2 . 1 1 4 . 2 2 . 9 8 0 8 02 0 2 . 1 1 4 . 2 2 . 9 8 0 8 0I P 地址 端口号S o c k e t图 1 Socket 与 IP 地址、端口号由于 TCP 面向连接的特性,如果多台主机或一台主机的多个进程连接同一台服务器,则必须创建多个连接,如图 2 所示。3/191 1 2 . 1
5、 1 3 . 1 1 1 . 1 61 1 2 . 1 1 3 . 1 1 1 . 1 6端口8 0 8 0端口8 0 8 4端口8 0 8 02 0 2 . 1 1 3 . 1 2 1 . 1 6 8端口8 0 8 0连接1连接2连接3图 2 与同一台主机建立三个连接TCP/IP 标准指定了一个概念层接口,包含了一系列过程和函数。标准建议了每个过程和函数所需要的参数及其所执行操作的语义,但没有进一步指定数据表示的细节。详细的接口通常由操作系统来定义,只要完成 TCP/IP 标准中的功能,可以有不同的细节选择。这样,不同的操作系统的应用程序编程接口是各不相同的。例如,广泛使用的 Berkele
6、y Software Distribution UNIX 的 Socket 接口、Windows的接口定义 Winsock、System V 的接口定义 TLI 接口等。3.3 Socket 的操作方式Socket 有两种主要的操作方式:面向连接和面向无连接。面向连接的 BSD UNIX Socket 的工作流程如图 3 所示,而面向无连接的 BSD UNIX Socket 的工作流程如图 4 所示。到底用哪种模式是由应用程序的需要决定的。如果要求可靠性,用面向连接的操作就会好一些。对于面向无连接的 C/S 模式,Socket 不需要连接目的地的Socket, 它只是简单地投出数据报。无连接的
7、操作简单高效,但数据的安全性不佳。4/19s o c k e t ( )b i n d ( )l i s t e n ( )a c c e p t ( )r e a d ( )w r i t e ( )c l o s e ( )s o c k e t ( )c o n n e c t ( )w r i t e ( )r e a d ( )c l o s e ( )服务器客户机建立连接请求数据应答数据阻塞 , 等待客户连接请求处理请求图 3 面向连接的 C/S 时序图s o c k e t ( )b i n d ( )l i s t e n ( )r e a d ( )w r i t e ( )
8、s o c k e t ( )c o n n e c t ( )w r i t e ( )r e a d ( )服务器客户机建立连接请求数据应答数据阻塞 , 等待客户连接请求处理请求c l o s e ( ) c l o s e ( )图 4 面向无连接的 C/S 时序图5/194、 TCP 通信程序设计4.1 编程要点由于 TCP 协议要求服务器和客户端建立连接,所以服务器需要通过 Listen 方法监听客户端的请求。当客户端发出连接请求后,服务器在 ConnectionRequest 事件中调用 Accept 方法接受请求,从而与客户端建立连接。只有双方建立连接后,才能进行数据的收发。如果
9、在通信过程中任一方断开连接,则通信过程终止。4.2 客户端程序客户端程序遵循以下步骤:1)建立客户端 Socket 连接。2)得到 Socket 读和写的流。3)操作流。4)关闭流。5)关闭 Socket。客户端的源程序代码如下:/ Module Name: Client.c/ Description:/ This sample is the echo client. It connects to the TCP server,/ sends data, and reads data back from the server. / Compile:/ cl -o Client Client.c
10、 ws2_32.lib/ Command Line Options:/ client -p:x -s:IP -n:x -o/ -p:x Remote port to send to/ -s:IP Servers IP address or hostname/ -n:x Number of times to send message/ -o Send messages only; dont receive/ #include 6/19#include #include #pragma comment(lib,“ws2_32“)#define DEFAULT_COUNT 20#define DEF
11、AULT_PORT 5150#define DEFAULT_BUFFER 2048#define DEFAULT_MESSAGE “This is a test of the emergency broadcasting system“char szServer128, / Server to connect toszMessage1024; / Message to send to severint iPort = DEFAULT_PORT; / Port on server to connect toDWORD dwCount = DEFAULT_COUNT; / Number of ti
12、mes to send messageBOOL bSendOnly = FALSE; / Send data only; dont receive/ Function: usage:/ Description:/ Print usage information and exit/void usage()printf(“usage: client -p:x -s:IP -n:x -onn“);printf(“ -p:x Remote port to send ton“);printf(“ -s:IP Servers IP address or hostnamen“);printf(“ -n:x
13、Number of times to send messagen“);printf(“ -o Send messages only; dont receiven“);ExitProcess(1);/ Function: ValidateArgs/ Description:/ Parse the command line arguments, and set some global flags / to indicate what actions to perform/void ValidateArgs(int argc, char *argv)7/19int i;for(i = 1; i 3)
14、iPort = atoi(break;case s: / Serverif (strlen(argvi) 3)strcpy(szServer, break;case n: / Number of times to send messageif (strlen(argvi) 3)dwCount = atol(break;case o: / Only send message; dont receivebSendOnly = TRUE;break;default:usage();break;/ / Function: main/ Description:/ Main thread of execu
15、tion. Initialize Winsock, parse the / command line arguments, create a socket, connect to the / server, and then send and receive data./int main(int argc, char *argv)WSADATA wsd;8/19SOCKET sClient;char szBufferDEFAULT_BUFFER;int ret,i;struct sockaddr_in server;struct hostent *host = NULL;/ Parse the
16、 command line and load Winsock/ValidateArgs(argc, argv);if (WSAStartup(MAKEWORD(2,2), return 1;strcpy(szMessage, DEFAULT_MESSAGE);/ Create the socket, and attempt to connect to the server/sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (sClient = INVALID_SOCKET)printf(“socket() failed: %dn“,
17、WSAGetLastError();return 1;server.sin_family = AF_INET;server.sin_port = htons(iPort);server.sin_addr.s_addr = inet_addr(szServer);/ If the supplied server address wasnt in the form/ “aaa.bbb.ccc.ddd“ its a hostname, so try to resolve it/if (server.sin_addr.s_addr = INADDR_NONE)host = gethostbyname(
18、szServer);if (host = NULL)printf(“Unable to resolve server: %sn“, szServer);return 1;CopyMemory(9/19if (connect(sClient, (struct sockaddr *)return 1;/ Send and receive data /for(i = 0; i #include #include #pragma comment(lib,“ws2_32“)#define DEFAULT_PORT 5150#define DEFAULT_BUFFER 4096int iPort = DE
19、FAULT_PORT; / Port to listen for clients onBOOL bInterface = FALSE, / Listen on the specified interfacebRecvOnly = FALSE; / Receive data only; dont echo backchar szAddress128; / Interface to listen for clients on/ Function: usage/ Description:/ Print usage information and exit/void usage()13/19print
20、f(“usage: server -p:x -i:IP -onn“);printf(“ -p:x Port number to listen onn“);printf(“ -i:str Interface to listen onn“);printf(“ -o Dont echo the data backnn“);ExitProcess(1);/ Function: ValidateArgs/ Description:/ Parse the command line arguments, and set some global flags / to indicate what actions
21、 to perform/void ValidateArgs(int argc, char *argv)int i;for(i = 1; i 3)strcpy(szAddress, break;case o:bRecvOnly = TRUE;break;default:usage();break;14/19/ Function: ClientThread/ Description:/ This function is called as a thread, and it handles a given/ client connection. The parameter passed in is
22、the socket / handle returned from an accept() call. This function reads/ data from the client and writes it back./DWORD WINAPI ClientThread(LPVOID lpParam)SOCKET sock=(SOCKET)lpParam;char szBuffDEFAULT_BUFFER;int ret,nLeft,idx;while(1)/ Perform a blocking recv() call/ret = recv(sock, szBuff, DEFAULT
23、_BUFFER, 0);if (ret = 0) / Graceful closebreak;else if (ret = SOCKET_ERROR)printf(“recv() failed: %dn“, WSAGetLastError();break;szBuffret = 0;printf(“RECV: %sn“, szBuff);/ If we selected to echo the data back, do it /if (!bRecvOnly)nLeft = ret;idx = 0;/ Make sure we write all the data15/19/while(nLe
24、ft 0)ret = send(sock, if (ret = 0)break;else if (ret = SOCKET_ERROR)printf(“send() failed: %dn“, WSAGetLastError();break;nLeft -= ret;idx += ret;return 0;/ Function: main/ Description:/ Main thread of execution. Initialize Winsock, parse the/ command line arguments, create the listening socket, bind
25、/ to the local address, and wait for client connections./int main(int argc, char *argv)WSADATA wsd;SOCKET sListen,sClient;int iAddrSize;HANDLE hThread;DWORD dwThreadId;struct sockaddr_in local,client;ValidateArgs(argc, argv);if (WSAStartup(MAKEWORD(2,2), return 1;/ Create our listening socket/sListe
26、n = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);if (sListen = SOCKET_ERROR)printf(“socket() failed: %dn“, WSAGetLastError();return 1;/ Select the local interface and bind to it/if (bInterface)local.sin_addr.s_addr = inet_addr(szAddress);if (local.sin_addr.s_addr = INADDR_NONE)usage();elselocal.sin_addr
27、.s_addr = htonl(INADDR_ANY);local.sin_family = AF_INET;local.sin_port = htons(iPort);if (bind(sListen, (struct sockaddr *)return 1;listen(sListen, 8);/ In a continous loop, wait for incoming clients. Once one / is detected, create a thread and pass the handle off to it./while (1)iAddrSize = sizeof(c
28、lient);sClient = accept(sListen, (struct sockaddr *) if (sClient = INVALID_SOCKET) 17/19printf(“accept() failed: %dn“, WSAGetLastError();break;printf(“Accepted client: %s:%dn“, inet_ntoa(client.sin_addr), ntohs(client.sin_port);hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)sClient, 0, if (hT
29、hread = NULL)printf(“CreateThread() failed: %dn“, GetLastError();break;CloseHandle(hThread);closesocket(sListen);WSACleanup();return 0;与客户端程序相比,服务器端在以下几个方面存在差异。1、指定本地地址bind()当一个套接字用 socket()创建后,存在一个名字空间(地址族),但它没有被命名。bind()将套接字地址(包括本地主机地址和本地端口地址)与所创建的套接字号联系起来,即将名字赋予套接字,以指定本地半相关。其调用格式如下:bind(SOCKET s,
30、 const struct sockaddr FAR * name, int namelen);参数 s 是由 socket()调用返回的并且未作连接的套接字描述符(套接字号)。参数 name 是赋给套接字 s 的本地地址(名字) ,其长度可变,结构随通信域的不同而不同。namelen 表明了 name 的长度。如果没有错误发生,bind()返回 0。否则返回值 SOCKET_ERROR。地址在建立套接字通信过程中起着重要作用,作为一个网络应用程序设计者对套接字地址结构必须有明确认识。例如,UNIX BSD 有一组描述套接字地址的数据结构,其中使用 TCP/IP 协议的地址结构为:struct
31、 sockaddr_in short sin_family; /*AF_INET*/18/19u_short sin_port; /*16 位端口号,网络字节顺序 */struct in_addr sin_addr; /*32 位 IP 地址,网络字节顺序*/char sin_zero8; /*保留*/2、监听连接 - listen()此调用用于面向连接服务器,表明它愿意接收连接。listen()需在 accept()之前调用,其调用格式如下:listen(SOCKET s, int backlog);参数 s 标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求。backlog
32、 表示请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为 5。如果没有错误发生, listen()返回 0。否则它返回SOCKET_ERROR。listen()在执行调用过程中可为没有调用过 bind()的套接字 s 完成所必须的连接,并建立长度为 backlog 的请求连接队列。调用 listen()是服务器接收一个连接请求的四个步骤中的第三步。它在调用socket()分配一个流套接字,且调用 bind()给 s 赋于一个名字之后调用,而且一定要在 accept()之前调用。3、accept()调用accept(SOCKET s, struct sockaddr FAR* a
33、ddr, int FAR* addrlen);accept()用于面向连接服务。参数 addr 和 addrlen 存放客户方的地址信息。调用前,参数 addr 指向一个初始值为空的地址结构,而 addrlen 的初始值为 0;调用 accept()后,服务器等待从编号为 s 的套接字上接受客户连接请求,而连接请求是由客户方的 connect()调用发出的。当有连接请求到达时,accept()调用将请求连接队列上的第一个客户方套接字地址及长度放入 addr 和 addrlen,并创建一个与s 有相同特性的新套接字号。新的套接字可用于处理服务器并发请求。参数 s 为本地套接字描述符,在用做 ac
34、cept()调用的参数前应该先调用过listen()。addr 指向客户方套接字地址结构的指针,用来接收连接实体的地址。addr 的确切格式由套接字创建时建立的地址族决定。 addrlen 为客户方套接字地址的长度(字节数) 。如果没有错误发生,accept()返回一个 SOCKET 类型的值,19/19表示接收到的套接字的描述符。否则返回值 INVALID_SOCKET。5、实验任务1、两人一组,分别编写网络程序 Server 和 Client,以实现最简单的 TCP 通信。说明:如果使用提供的 Server.c 和 Client.c,则在文件菜单中读入程序后,可直接“Build”成执行文件
35、运行。当询问“This build command requires an active project workspace ?”时,单击“确定”按钮即可。2、在 VC+ 6.0 集成环境下单步调试程序,参考Winsock 基础 ,弄清楚程序中相关函数的用法、每个数据结构的含义。6、提交实验报告1. 画出你所写程序的框图。2. 在报告中说明你所修改后程序的任何独特之处。3. 实验日期:第 18 周星期五(2010-7-2)晚上 7:00 10:00。实验地点:南 3 楼 网络中心机房。4. 实验报告(含框图、修改说明及程序源代码)通过电子文档形式提交:以“学号-姓名-TCP 通信”为文件名,发送至:。截至时间:2010-7-9 17: 00 整。