通过 TCP 套接字编程实验,掌握网络套接字的构成,掌握 TCP 的通信特点,理解面向连接的通信方式;实现基于 TCP 的服务器/客户端通信过程。
能够连接网络的计算机一台;高级程序设计软件如 Microsoft Visual C++ 6.0 或 Code::Blocks。
连接只能有两个端点(endpoint),每一条连接只能是点对点的(一对一)。 是面向连接的运输层协议。提供全双工通信。提供可靠交付的服务;面向字节流 (1)提供协议端口来保证进程通信; (2)提供面向连接的全双工数据传输; (3)提供高可靠的按序传送数据的服务。 (2) 基于 TCP 的服务器/客户端通信,服务器与客户端的程序流程应该是怎么样的?请画出流程图。 服务器: 创建socket对象 使用bind()绑定主机号host和端口号port 使用listen()监听 使用accept()被动连接 使用send()/rece() 发送/接收数据 客户端: 创建socket对象 使用connect连接到服务器端 使用send()/recv() 发送/接收数据 关闭socket对象
socket() socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor)当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。 bind(), bind()函数把一个地址族中的特定地址赋给socket。 listen() 第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。 connect(), 客户端通过调用connect函数来建立与TCP服务器的连接 accept(), accept()是在一个套接口接受的一个连接。从s的等待连接队列中抽取第一个连接,创建一个与s同类的新的套接口并返回句柄 send(), 功能是向一个已经连接的socket发送数据,如果无错误,返回值为所发送数据的总数,否则返回SOCKET_ERROR recv() 使用该函数可以方便地接收网络数据,从而实现各种网络应用程序
通过编写 TCP 套接字程序,采用 C 程序设计语言,实现 TCP 的服务器/客户端相互之间数据的传送与接受。 (1)、编写程序时,在创建套接字 socket 时,采用socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)函数,函数的三个参数分别表示:Socket 的地址域,分为 unix(AF_UNIX,表示 unix 系统的两个进程可以通信)和 Internet(AF_INET,表示 Internet 上的任意两台主机可以通信);Socket 的类型,UDP 是数据报形式,DGRAM,而 TCP是流形式 SOCK_STREAM;协议类型,通常由系统自行设定是 TCP 还是 UDP; 结构体类型 sockaddr_in,是包括 Internet 地址的、含有四个域的类型; 函数 bind()将一个 Socket 绑定到特定的 IP 地址和端口上; 函数 listen( )表示特点的端口上进行侦听; 函数 connect()表示想特定的套接字发起连接; 函数 accept()表示有连接请求并且接受连接; 函数 send()表示向特定的 socket 地址发送数据; 函数 recv()表示从特定的 socket 地址接受数据。 (2)测试代码。打开 VC6.0,新建工程,分别启动服务端、客户端应用程序,在本地机器上实现 TCP 数据的发送与接收; (3)测试两台主机,实现 TCP 数据的发送与接受;
(相关的关键代码需写在实验报告上)。
(相关的关键代码需写在实验报告纸上 服务器: #include <winsock2.h> #include <stdio.h> #include <string.h> #pragma comment(lib,"ws2_32.lib") typedef struct sockaddr_in sockaddr_in; typedef struct WSAData WSAData; typedef struct sockaddr sockaddr; int main(int argc, char* argv[]) {//初始化WSA WORD sockVersion = MAKEWORD(2,2); WSADATA wsaData; if(WSAStartup(sockVersion, &wsaData)!=0) { return 0; } //创建套接字 SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(slisten == INVALID_SOCKET) { printf("socket error !"); return 0; } //绑定IP和端口 sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(8080); sin.sin_addr.S_un.S_addr = INADDR_ANY; if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) //if(bind(slisten, (sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR) { printf("bind error !"); } //开始监听 if(listen(slisten, 5) == SOCKET_ERROR) { printf("listen error !"); return 0; } SOCKET sClient; sockaddr_in remoteAddr; int nAddrlen = sizeof(remoteAddr); printf("等待连接...\n"); sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen); if(sClient == INVALID_SOCKET) { printf("accept error !"); closesocket(sClient); closesocket(slisten); WSACleanup(); return 0; } printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr)); int i=0,j,tmp; char buf[255]={0}; char revData[255]; printf("\n实现字符数组的翻转与加解密操作\n"); while(1) { printf("\n等待客户端发送数据\n"); int ret = recv(sClient,revData,255,0); if(ret>0) { revData[ret] = 0x00; printf("\n接收到的数据: "); printf(revData); printf("\n"); if(strcmp(revData,"next")==0) { break; } while(revData[i] !='\0') { i++; } tmp = i; for(j=0;j<tmp;j++,i--) { buf[j]=revData[i-1]; } printf("\n反转后的数据: "); printf(buf); printf("\n"); for(i=0;i<strlen(revData);i++) {if(revData[i]>='A'&&revData[i]<='Z') { revData[i]=((revData[i]-'A')+26-4)%26+'A'; } else if(revData[i]>='a'&&revData[i]<='z') { revData[i]=((revData[i]-'a')+26-4)%26+'a'; } } printf("\n解密后的数据: "); printf(revData); printf("\n"); } for(i=0;i<strlen(buf);i++) { if(buf[i]>='A'&&buf[i]<='Z') { buf[i]=((buf[i]-'A')+26-4)%26+'A'; } else if(buf[i]>='a'&&buf[i]<='z') { buf[i]=((buf[i]-'a')+26-4)%26+'a'; } } printf("\n反转后的的加密数据: "); printf(revData); printf("\n"); send(sClient,buf,strlen(buf),0); } printf("\n**********聊天**********\n"); while(1) {char str1[50]; char str2[50]; int ret = recv(sClient, str2, 255, 0); if(strcmp(str2,"byebye")==0)//判断 { break; } if(ret > 0) { str2[ret] = 0x00; printf("\n来自client: "); printf(str2); printf("\n"); } printf("sever: "); if(scanf("%s", str1)) { printf("\n"); send(sClient, str1, strlen(str1), 0); if(strcmp(str1,"byebye")==0)//判断 { break; } } } printf("\n开始退出\n"); closesocket(slisten); closesocket(sClient); WSACleanup(); return 0; } 客户端: #include <winsock2.h> #include <stdio.h> #include <string.h> #pragma comment(lib,"ws2_32.lib") typedef struct sockaddr_in sockaddr_in; typedef struct WSAData WSAData; typedef struct sockaddr sockaddr; int main(int argc, char* argv[]) { WORD sockVersion = MAKEWORD(2,2); WSADATA data; if(WSAStartup(sockVersion, &data) != 0) { return 0; } SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(sclient == INVALID_SOCKET) { printf("invalid socket !"); return 0; } sockaddr_in serAddr; serAddr.sin_family = AF_INET; serAddr.sin_port = htons(8080); serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) { printf("connect error !"); closesocket(sclient); return 0; } int i; int len = sizeof(serAddr); char sentData[255]; char recData[255]; printf("\n实现字符数组的翻转与加解密操作\n"); while(1)//实现字符数组的翻转与加解密操作 { printf("\n发送数据\n"); gets(sentData); if(strcmp(sentData,"next")!=0) { for(i=0; i<strlen(sentData); i++)//加密 { if(sentData[i] >= 'A' && sentData[i] <= 'Z') { sentData[i] = ((sentData[i]-'A')+4)%26+'A'; } else if(sentData[i] >= 'a' && sentData[i] <= 'z') { sentData[i] = ((sentData[i]-'a')+4)%26+'a'; } } printf("\n加密后: "); printf(sentData); printf("\n"); send(sclient, sentData, strlen(sentData), 0); int ret = recv(sclient, recData, 255, 0); if(ret > 0) { recData[ret] = 0x00; for(i=0; i<strlen(recData); i++)//原字符解密 { if(recData[i] >= 'A' && recData[i] <= 'Z') { recData[i] = ((recData[i]-'A')+26-4)%26+'A'; } else if(recData[i] >= 'a' && recData[i] <= 'z') { recData[i] = ((recData[i]-'a')+26-4)%26+'a'; } } printf("\n解密翻转后的数据: "); printf(recData); printf("\n"); } } else { strcpy(sentData,"next"); sendto(sclient, sentData, strlen(sentData), 0, (sockaddr *)&serAddr, len); break; } } printf("\n**********聊天**********\n"); while(1)//实现基于 TCP 的聊天软件设计 { char str1[50]; char str2[50]; printf("\nclient: "); if(scanf("%s",str1)) { send(sclient,str1,strlen(str1),0); } if(strcmp(str1,"byebye")==0) { break; } int ret = recv(sclient,str2,255,0); if(ret>0) { str2[ret]=0x00; printf("来自sever: "); printf(str2); printf("\n"); if(strcmp(str2,"byebye")==0) { break; } } } closesocket(sclient); WSACleanup(); return 0; } (1)、标识 TCP 套接字的四元组是什么?TCP 通信能否实现一个套接字对多个套接字的通信方式?为什么? 答:源IP,源端口,目标IP,目标端口 TCP通信是一对一的点对点通信方式,一个套接字只能与一个套接字进行通信。要实现一个套接字与多个套接字的通信,可以使用并发编程技术,创建多个线程或进程来处理多个连接。 (2)、列举 3 种以上采用 TCP 传输层协议的网络应用程序。 答:TELNET,FTP,客户机/服务器模式,基于tcp的socket编程。 (3)、TCP 的拥塞控制有什么优点和缺点? 优点:若对网络中某一个资源的需求超过了该资源所能提供的可用部分,网络性能就要降低。轻度拥塞状态: 网络的吞吐量明显小于理想的吞吐量时进入拥塞状态: 若出现拥塞而不控制,整个网络的吞吐量将随输入负荷的增大而下降 缺点:死锁: 吞吐量为0时,网络将无法工作
在进行TCP套接字编程实验后,我得出以下总结:TCP套接字编程可用于实现客户端和服务器之间的通信。服务器端通过绑定地址、监听连接请求、接受连接和与客户端通信来提供服务,而客户端通过创建套接字对象、连接服务器、发送和接收数据来与服务器进行通信。还有就是代码的输入中英文区分,和大小之间的关系是32位在大小写直接进行转换以及反转大小写。TCP套接字编程能够提供可靠的数据传输和连接管理,适用于需要确保数据完整性和可靠性的应用程序,如网页浏览、文件传输和实时通信等。 实验过程可以帮助我们加深对实验的理解,发现实验的优点和不足,并提出改进和进一步研究的建议。同时,也可以总结自己在实验中的收获和成长。 |
TCP 套接字编程
最新推荐文章于 2025-03-03 19:21:18 发布