《TCP程序设计(c语言课程设计)(共26页).doc》由会员分享,可在线阅读,更多相关《TCP程序设计(c语言课程设计)(共26页).doc(26页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、精选优质文档-倾情为你奉上 第十章 TCP协议,即传输控制协议(Transport Control Protocol),是一种面向连接的、可靠的传输层协议。TCP协议是为了在主机实现高可性包交换的传输协议,在计算机网络中用途很广泛。本章将通过C程序语言编程来实现一个基于TCP协议的程序,旨在向读者介绍TCP的实现原理,并进一步向读者介绍C语言网络编程技术。关于TCP的原理知识,读者可参见第2章。10.1 设计目的本章通过C 语言编程实现了一个TCP程序包括服务器端程序和客户端程序,程序能实现基本的通信。通过本程序向读者展示了TCP的服务器端和客户端的操作流程,用以加深读者对TCP原理的理解。本
2、章的部分知识点在前面章节也有所涉及,读者可以由此加深印象。通过本章的学习,读者应该对以下知识点有一定的了解:Winsock版本的设置、Winsock库的加载以及Winsock错误号的获取;套接字的创建和关闭;TCP服务器的操作流程、客户端的操作流程;套接字的绑定、侦听、连接和接收操作;数据报的发送和接收;根据地址获取主机、根据主机名获取IP地址等信息;线程饿创建和参书设置;字符串比较函数的使用等。 读者可以在本章的基础上加以拓展,深刻理解TCP原理,掌握TCP编程方法和技巧,开发出自己的TCP程序。10.2 功能描述 本章用C语言实现了基于TCP的服务器端和客户端程序,能实现基本的TCP通信。
3、其主要的功能包括如下。(1) 服务器端能以默认选项(服务器端IP地址或主机名、端口号)启动,提供服务功能。(2) 服务器端能根据用户指定的选项(服务器端IP地址或主机名、端口号)启动,提供服务和功能。(3) 服务器以错误选项启动时,会提示错误信息,并终止程序。(4) 客户端能连接到服务器端,发送消息到服务器端,同时也能接收来自服务器的响应。(5) 客户端不能连接到服务器端时,能输出错误信息。(6) 客户端以错误选项启动时,会提示错误信息,并终止程序。10.3总体设计10.3.1 功能模块设计1. 个功能模块图本程序由两大部分组成,包括服务器端和客户端,如图10.1所示。服务器端包含的模块有初始
4、模块、循环控制模块和服务模块;客户端包含的模块有初始化模块、功能控制模块和数据传输控制模块。1) 服务器端(1) 初始化模块用于初始化各个全局变量赋初始值。初始化Winsock,加载Winsock库。(2) 功能模块控制。该模块为其他模块提供调用的函数,包括参数获取功能、用户帮助功能和错误输出功能 。(3) 循环控制模块。该模块用于控制服务器端的服务次数,如果服务次数超过指定的值则停止服务器。(4) 服务模块。该模块为客户端提供服务功能,包括接收来自客户端的数据,并发送数据到客户端。2) 客户端(1) 初始化模块。该模块用于初始化客户端的Winsock,加载Winsock库。(2) 功能模块控
5、制。与服务器端一样,该模块提供了参数获取、用户帮助和错误输出功能。(3) 数据传输控制模块。该模块控制着整个客户端的数据传输,包括数据发送和接收等。TCP程序设计 客户端 服务器端初始化模块功能控制模块数据传输控制模块功能控制模块初始化模块服务模块循环控制模块 图 10.1 功能模块图2. 服务器端系统流程图服务器端系统流程图10.2所示。程序首先调用GetArgments()函数获取用户提供的先项,如果没有提供选项,则直接使用默认值,如果有选项提供并成功获取(选项错误则显示用户帮助并终止程序),则初始化变量和Winsock,并创建TCP流套接字;然后解析主机名(如果选项提供的是IP地址 ,或
6、者使用是默认值)或者IP地址(如果选项提供的是主机名),解析成功后则设置服务器地址的各个参数,包括地址、IP地址和端口号;接下来将创建的TCP流套接字和设定的服务器地址绑定(调用bing()函数);绑定成功后,则开始侦听客户的连接,调用循环控制函数LoopControl()函数和Service()函数作接收客户端的连接,接收数据、发送数据等操作;当服务数达到最多的服务次数时,并提示错误信息(调用ErrorPrint()函数实现)。开始获取参数获取成功否是初始化变量和Winsock 创建套接字创建成功解析主机名或者IP地址否是解析成功否是设置服务器地址参数绑定地址与接字绑定成功 侦听连接否是侦听
7、成功否是 循环控制输出相应错误信息释放资源关闭服务结束 图10.2 服务器端系统流程图3. 客户端系统流程图客户端系统流程图如图10.3所示。客户端程序执行时必须带选项,程序首先判断用户提供的参数个数,如果参数不等于3个,则比表明用户没有提供正确的选项,退出程序;如果参数等于3个,则调用GetArgments()函数获取用户提高的选项,如果获取的选项错误则显示用户帮助并终止程序,如果选项正确则开始创建TCP流套接字,成功创建TCP流套接字后则作和服务器类似的操作,即解析主机名或IP地址、设置服务器端地址;然后进行连接服务器操作,若能成功连接则输出连接信息,并发送消息到服务器端;然后接收来自服务
8、器端的响应,(消息),并将接收到的消息输出。最后关闭套接字和释放占用的资源。和服务器一样,在操作过程中,任何一步操作失败都将退出程序,并提示错误信息(调用ErrorPrint()函数实现)。开 始3个参数否是获 取 参 数显示用户帮 助获取成功否是创 建 套 接 字创建成功否是解析主机名或者IP地址解析成功否是设置服务器地址参数连 接服务器连接成功否是输出连接信息发送 信息 到服 务 器 端接收服务器端的响应输出相应错误信息释 放 资 源 关闭 套 接 字结束图 10.3 客户端系统流程图4. 循环控制模块(服务器端)该模块是服务器端用于循环控制的模块,其操作流程如图10.4所示。当服务器端侦
9、听到客户连接时,调用该模块进行操作。首先接收客户端的请求,接收成功后,根据传入的参数is Multitasking判断是否要创建一个线程来服务客户端,如果is Multitasking是1则创建线程来服务客户端(创建新线程时,设置了的初始堆栈大小为1000,线程执行函数是Service(),传递给Service()的参数为接收套接字),如果is Multitasking是0则直接调用 Service()函数来服务客户端。一次服务成功后,判断循环次数是否小于最大服务次数(可使用默认值,也可使用参数形式提供),如果已达到最大服务次数则关闭服务器,否则继续进行下一次服务。开 始接收客户端请求接收成功
10、?否是创建线程?否输出错误信息是直接调用服务函数创建线程设置参数和服务函数还可以服务?是否结 束图10.4 循环控制模块流程图5. 服务模块(服务器端) 服务模块用于在服务器端为客户端服务,该模块的实现较为简单,主要进行接受和发送数据操作,其实现流程如图10.5所示。首先用0初始化缓冲区response(数组),然后接收来自客户端的数据,判断接收到的数据是否是HELLO SERVER,如果不是则表示不是对应的客户端,如果是则发送数据到客户端。操作结束后关闭套接字。开 始初始 化 缓 冲区接收客户端数据是预定义的数据?否输出错误信息是发送消息到客户端关闭套接字结 束图 10.5 服务模块实现流程
11、图 6. 服务模块(服务器端) 服务模块用于在服务器端为客户端服务,该模块的实现较为简单,主要进行接受和发送数据操作,其实现流程如图10.5所示。首先用0初始化缓冲区response(数组),然后接收来自客户端的数据,判断接收到的数据是否是HELLO SERVER,如果不是则表示不是对应的客户端,如果是则发送数据到客户端。操作结束后关闭套接字。10.3.2数据结构设计本程序没有定义结构体,在此仅讲述服务器端和客户端定义的全局变量。1. 服务器端 在服务器端定义了3个全局变量,分别是指向字符的指针hostName、无符号短整型变量maxService和无符号短整型port,各自表示的意义如下。c
12、har *hostName:该指针用于接收主机名选项,可以是IP地址,也可以是主机名。Unsigned short maxServer:用于存储服务器端最大的服务次数,超过该次数,服务器将终止服务。Unsigned short port :用于存储服务器端提供的端口号。这3个变量所存储的值都是表示服务器启动时提供的选项,如果服务器启动时没有提供这些选项,程序将按照默认设置的值启动服务器。2. 客户端客户端提供了和服务器端累世的两个全局变量,气作用和意义都是和服务器端的相同,只是这两个变量存储的值在程序中没有默认值,需要客户端启动是提供相应的选项。char* hostName:接收主机名选项。U
13、ndigned short port:用以存储服务客户端提供的端口号。10.3.3函数功能描述1. Initial()函数原型:void initial()Initial()函数用于初始化服务器端的全局变量,包括hostName、maxServerice和port,分别被初始化为“127.0.0.1”、“3”和“9999”。服务器在启动时,若没有指定这些选项,程序将使用这些默认值启动服务器。2. InitSockets()函数原型:int InitSockts(void)InitSockets()函数用于初始化Winsock。3. GetArgment()函数原型:viod GetArgmen
14、t(int argc,char*argv)GetArgment()函数用于获取用户提供的选项,在服务器端能获取的参数包括主机名(或IP地址)、最多服务次数和端口号。其中argc表示获取的选项个数,argv用来存储获取的选项值,这个参数的值通过主函数的参数传递过来。4. ErrorPrint()函数原型:void ErrorPoint(x)ErrorPoint()函数用于输出错误信息,该函数调用系统函数WSAGetLastError()来获取错误号。其中X表示错误消息。5. userHelp()函数原型:void userHelp()userHelp()函数用于现实用户帮战。当服务器端启动时,若
15、提供的选项错误,将调用该函数输出用户帮助信息,提供的信息包括选项的格式和类型。6. LoopControl()函数原型:int LoopControl(SOCKET listenfd,int isMultiTasking)LoopControl()函数用于循环控制,当服务器的服务次数在指定的范围内,将接收客户端的请求,并创建一个线程(如果需要的话)来为客户端服务(调用Service()函数)。其中listenfd表示侦听套接字,isMultiTasking是个标记,如果其设置为1,则创建一个线程来服务器端,如果其设置为0,则直接调用服务器函数来服务客户端。7. Service()函数原型:vo
16、id Service(LPVOID lpv)Service()函数用于服务客户端,包括接收客户端的数据和发送数据到客户端。2. 客户端客户端的这几个函数在服务器端也出现过,其功能和服务器端的函数类似。1 InitSockets()函数原型:int InitSockets(void)InitSockets()函数用于初始化Winsock。2 GetArgument()函数原型:void GetArgument(int argc , char *argv)GetArguemnt()函数用于获取用户提供的选项,在客户端能获取的参数包括主机名(或IP地址)和端口号。其中argc和argv值也是通过主函
17、数的参数传递过来,其表示的意义和主函数中的一样。3 ErrorPrint()函数原型:void Errorprint()ErrorPrint()函数用于输出错误信息。4 userHelp()函数原型:void userHelp()userHelp()函数用于显示用户帮助。当客户端不带选项启动时或带错误选项启动时将调用该函数显示用户帮助,显示选项的格式和类型。10.4程序实现10.4.1 源码分析1 服务端(service.c)1 程序预处理程序处理包括库文件的导入、头文件的加载以及常量和全局变量的定义 /*导入库文件*/#pragma comment(lib,wsock32.lib)/*加载头
18、文件*/#include #include /*自定义函数原型*/void initial();int InitSockets(void);void GetArgments(int argc, char *argv);void ErrorPrint(x);void userHelp();int LoopControl(SOCKET listenfd, int isMultiTasking);void Service(LPVOID lpv);/*定义常量*/#define MAX_SER 10/*定义全局变量*/char *hostName;unsigned short maxService;u
19、nsigned short port;2初始化模块初始化模块由两部分组成,包括全局变量的初始化和Winsock的初始化,由两个函数来实现1 void initial(),初始化全局变量,其中hostName被赋值为“127.0.0.1”,表明程序在运行是仅限制客户端和服务器端在同一主机上进行,如果要改变该值,需要在服务器启动是,设置hostName选项,重新赋值;maxService表示最大的服务次数,其值应该不大于MAX SER代表的值。2 int InitSockets(void),初始化Winsock,包括初始化套接字版本号和加载Winsockku。/*初始化全局变量函数*/void i
20、nitial() hostName = 127.0.0.1; maxService = 3; port = 9999;/*初始化Winsocket函数*/int InitSockets(void) WSADATA wsaData; WORD sockVersion; int err; /*设置Winsock版本号*/ sockVersion = MAKEWORD( 2, 2 ); /*初始化Winsock*/ err = WSAStartup( sockVersion, &wsaData ); /*如果初始化失败*/ if ( err != 0 ) printf(Error %d: Winso
21、ck not availablen, err); return 1; return 03. 功能控制模块功能控制模块提供了参数获取功能、错误输出功能和用户帮助功能,这几个功能分别由GetArgments(int argc ,char *argv),获取用户提供的选项值。该函数首先判断每个参数的第一个字符,如果第一个字符是“-”(短横线)则表示该参数是用户提供的选项。提供的选项包括“-p(-p)”,表示端口号;“-h(-H)”,表示主机名(或者ip地址);“-n(-N)”,表示服务器端的最多服务次数,超过该服务次数服务器将自动停止。(2) void ErrorPrint(x),错误输出函数。(3
22、) void userHelp(),显示用户帮助函数。在GetArgments()函数中,如果获取的选项值不是预定义的值,则调用该函数输出用户帮助。/*获取选项函数*/void GetArgments(int argc, char *argv) int i; for(i=1; i 3) port = atoi(&argvi3); break; /*若是主机名*/ case h: hostName = &argvi3; break; /*最多服务次数*/ case n: maxService = atoi(&argvi3); break; /*其他情况*/ default: userHelp()
23、; break; return;/*错误输出函数*/void ErrorPrint(x) printf(Error %d: %sn, WSAGetLastError(), x);/*用户帮助函数*/void userHelp() printf(userHelp: -h:str -p:int -n:intn); printf( -h:str The host name n); printf( The default host is 127.0.0.1n); printf( -p:int The Port number to usen); printf( The default port is 9
24、999n); printf( -n:int The number of service,below MAX_SER n); printf( The default number is 3n); ExitProcess(-1);4) 循环控制模块 循环控制模块的功能是由LoopControl()函数实现的。具体步骤可参见10.3.3节中的函数功能描述其操作流程图可参见图10.4。/*循环控制函数*/int LoopControl(SOCKET listenfd, int isMultiTasking) SOCKET acceptfd; struct sockaddr_in clientAddr;
25、 int err; int nSize; int serverNum = 0; HANDLE handlesMAX_SER; int myID; /*服务次数小于最大服务次数*/ while (serverNum maxService) nSize = sizeof(clientAddr); /*接收客户端请求*/ acceptfd = accept(listenfd, (struct sockaddr *) &clientAddr, &nSize); /*如果接收失败*/ if (acceptfd = INVALID_SOCKET) ErrorPrint(Error: accept fail
26、edn); return 1; /*接收成功*/ printf(Accepted connection from client at %sn, inet_ntoa(clientAddr.sin_addr); /*如果允许多任务执行*/ if (isMultiTasking) /*创建一个新线程来执行任务,新线程的初始堆栈大小为1000,线程执行函数 是Service(),传递给Service()的参数为acceptfd*/ handlesserverNum = CreateThread(NULL, 1000, (LPTHREAD_START_ROUTINE)Service, (LPVOID)
27、acceptfd, 0, &myID); else /*直接调用服务客户端的函数*/ Service(LPVOID) acceptfd); serverNum+; if (isMultiTasking) /*在一个线程中等待多个事件,当所有对象都被通知时函数才会返回,并且等待没有时间限制*/ err = WaitForMultipleObjects(maxService, handles, TRUE, INFINITE); printf(Last thread to finish was thread #%dn, err); return 0;5) 服务模块 服务模块的功能由函数Service
28、()来实现。其功能主要是接收、判断来自客户端的数据,以及发送数据到客户端。Service()函数首先接收客户端发送来的数据,存放到缓冲区response中,然后判断接收到的数据是否和预定义的数据“HELLO SERVER”相同,如果相同则发送消息到客户端,并关闭套接字;否则,输出错误信息并关闭套接字。其实现流程图可参见图10.5。/*服务函数*/void Service(LPVOID lpv) SOCKET acceptfd = (SOCKET) lpv; const char *msg = HELLO CLIENT; char response4096; /*用0初始化response409
29、6数组*/ memset(response, 0, sizeof(response); /*接收数据,存入response中*/ recv(acceptfd, response, sizeof(response), 0); /*如果接收到的数据和预定义的数据不同*/ if (strcmp(response, HELLO SERVER) printf(Application: client not using expected protocol %sn, response); else /*发送服务器端信息到客户端*/ send (acceptfd, msg, strlen(msg)+1, 0)
30、; /*关闭套接字*/ closesocket(acceptfd);6) 主函数 主函数控制着整个程序的流程,包括套接字的创建、绑定、侦听和释放,以及对各个模块中函数的调用等。其具体操作流程图可参见图10.2。/*主函数*/int main(int argc, char *argv) SOCKET listenfd; int err; struct sockaddr_in serverAddr; struct hostent *ptrHost; initial(); GetArgments(argc,argv); InitSockets(); /*创建TCP流套接字,在domain参数为PF_
31、INET的SOCK_STREAM套接口中,protocol参数为0意味 着告诉内核选择 IPPRPTP_TCP,这也意味着套接口将使用TCP/IP协议*/ listenfd = socket(PF_INET, SOCK_STREAM, 0); /*如果创建套接字失败*/ if (listenfd = INVALID_SOCKET) printf(Error: out of socket resourcesn); return 1; /*如果是IP地址*/ if (atoi(hostName) /*将IP地址转换成32二进制表示法,返回32位二进制的网络字节序*/ u_long ip_addr
32、= inet_addr(hostName); /*根据IP地址找到与之匹配的主机名*/ ptrHost = gethostbyaddr(char *)&ip_addr, sizeof(u_long), AF_INET); /*如果是主机名*/ else /*根据主机名获取一个指向hosten的指针,该结构中包含了该主机所有的IP地址*/ ptrHost = gethostbyname(hostName); /*如果解析失败*/ if (!ptrHost) ErrorPrint(cannot resolve hostname); return 1; /*设置服务器地址*/ /*设置地址族为PF_
33、INET*/ serverAddr.sin_family = PF_INET; /*将一个通配的Internet地址转换成无符号长整型的网络字节序数*/ serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); /*将端口号转换成无符号短整型的网络字节序数*/ serverAddr.sin_port = htons(port); /*将套接字与服务器地址绑定*/ err = bind(listenfd, (const struct sockaddr *) &serverAddr, sizeof(serverAddr); /*如果绑定失败*/ if (err
34、 = INVALID_SOCKET) ErrorPrint(Error: unable to bind socketn); return 1; /*开始侦听,设置等待连接的最大队列长度为SOMAXCONN,默认值为5个*/ err = listen(listenfd, SOMAXCONN); /*如果侦听失败*/ if (err = INVALID_SOCKET) ErrorPrint(Error: listen failedn); return 1; LoopControl(listenfd, 1); printf(Server is downn); /*释放Winscoket初始化时占用的
35、资源*/ WSACleanup(); return 0; 117.73.130.1622. 客户端(client.c)1) 程序预处理 与服务器一样,客户端的预处理也包括库文件的导入、头文件的加载和全局变量的定义。/*导入库文件*/#pragma comment(lib,wsock32.lib)/*加载头文件*/#include #include /*自定义函数*/int InitSockets(void);void GetArgument(int argc, char *argv);void ErrorPrint(x);void userHelp();/*定义全局变量*/unsigned s
36、hort port;char *hostName;2) 初始化模块 因为不存在对全局变量赋初始值,所以客户端的初始化模块仅仅初始化Winsock,包括初始化套接字版本号加载Winsock库。/*初始化Winsock函数*/int InitSockets(void) WSADATA wsaData; WORD sockVersion; int err;/*设置Winsock版本号*/ sockVersion = MAKEWORD( 2, 2 ); /*初始化Winsock*/ err = WSAStartup( sockVersion, &wsaData ); /*如果初始化失败*/ if (
37、err != 0 ) printf(Error %d: Winsock not availablen, err); return 1; return 0;3) 功能控制模块 功能控制模块包括选项获取功能、错误输出功能和用户帮助功能。这几个功能分别由GetArgment()函数、ErrorPrint()函数和userHelp()函数来实现,这几个函数和服务器端的函数功能、参数意义相同,在此就不再赘述。(1) void GetArgment(int argc,char *argv),获取用户提供的选项。(2) void ErrorPrint(x),输出错误信息。(3) void userHelp(
38、),显示用户帮助。/*获取选项函数*/void GetArgments(int argc, char *argv) int i; for(i=1; i 3) port = atoi(&argvi3); break; /*若是主机名*/ case h: hostName = &argvi3; break; /*其他情况*/ default: userHelp(); break; return;/*错误输出函数*/void ErrorPrint(x) printf(Error %d: %sn, WSAGetLastError(), x);/*用户帮助函数*/void userHelp() printf(us