TCP多线程非阻塞文件传输(文件流形式传输,支持对多客户进行 超大文件的传输)
- 格式:doc
- 大小:59.00 KB
- 文档页数:11
using namespace std; #include tcp服务器端使用多线程技术同时与多个客户通信的编程方法在TCP服务器端使用多线程技术同时与多个客户通信,通常需要使用一些编程语言和框架来实现。 以下是一个使用Python和其标准库中的socket 和threading模块来实现的简单示例:```pythonimport socketimport threading创建一个socket对象server_socket = (_INET, _STREAM)绑定到特定的IP地址和端口server_(('',监听连接,最大连接数为10server_(10)存储线程的列表threads = []def handle_client(client_socket):"""处理客户端连接的函数"""while True:接收客户端发送的数据data = client_(1024)if not data:break处理数据...print(f"Received from client: {()}")关闭客户端连接client_()while True:接受客户端的连接请求,并返回一个新的socket对象(用于与该客户端通信)client_socket, address = server_()print(f"Connection from {address} has been established!") 创建新线程来处理这个客户端的连接thread = (target=handle_client, args=(client_socket,))() 开始线程(thread) 将线程添加到线程列表中等待所有线程完成(即等待所有客户端连接关闭)for thread in threads:()关闭服务器端socketserver_()```这个示例创建了一个TCP服务器,它监听本地的12345端口。 网络协议知识:SCTP协议和TCP协议的应用场景和优缺点SCTP协议和TCP协议是两种常见的网络传输协议,它们在实际的网络通信中有着不同的应用场景和优缺点。 本文将分别介绍SCTP协议和TCP协议的特点,然后对它们进行比较,以便更好地了解它们的应用场景和优缺点。 SCTP协议(Stream Control Transmission Protocol)是一种可靠的、面向消息的传输层协议,它最早由IETF(Internet Engineering Task Force)在RFC 2960中定义。 SCTP协议的主要特点包括支持多路复用、多流传输、消息边界保持和部分可靠传输等。 SCTP协议最初是为了替代TCP协议而设计的,主要用于一些对传输可靠性要求比较高的应用场景,比如VoIP(Voice over IP)、实时多媒体通信和移动通信等。 TCP协议(Transmission Control Protocol)是一种可靠的、面向连接的传输层协议,它最早由IETF在RFC 793中定义。 TCP协议的主要特点包括面向连接、可靠传输、流控制和拥塞控制等。 TCP协议是目前广泛应用的传输层协议,它支持大多数的应用场景,比如Web访问、邮件传输、文件下载和远程登录等。 下面我们将分别对SCTP协议和TCP协议进行更详细的介绍。 SCTP协议的特点SCTP协议是一种可靠的传输层协议,它支持多路复用,可以同时在一个连接上传输多个独立的数据流。 这使得SCTP协议可以在一条连接上同时传输多个业务数据,从而提高了网络资源的利用率。 在实际的网络通信中,有很多应用场景需要同时传输多个数据流,比如VoIP 通信需要同时传输音频和视频数据,而SCTP协议可以很好地满足这种需求。 另外,SCTP协议还支持消息边界保持,这意味着在发送端可以保留消息的边界信息,在接收端可以按照发送顺序逐个接收消息。 这对于许多应用场景来说非常重要,比如即时通讯和实时多媒体通信等。 TCP协议详解一、引言TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层协议。 它是互联网协议套件中的核心协议之一,用于在网络上可靠地传输数据。 本协议详解将对TCP协议的工作原理、特点以及相关机制进行详细说明。 二、协议概述1. 协议定义TCP协议是一种面向连接的协议,通过建立可靠的通信连接来传输数据。 它提供了数据分段、数据重组、流量控制、拥塞控制等功能,保证了数据的可靠性和顺序性。 2. 连接建立与终止TCP协议使用三次握手建立连接,即客户端发送连接请求,服务器回复连接确认,最后客户端再次回复确认。 连接终止时,采用四次挥手的方式,即一方发送连接释放请求,另一方回复确认,然后另一方再次发送连接释放请求,最后一方回复确认。 3. 数据分段与重组TCP协议将应用层传输的数据分成小的数据段进行传输,每个数据段都有序号和确认号。 接收方根据序号和确认号进行数据的重组和排序,确保数据的完整性和正确性。 4. 流量控制与拥塞控制TCP协议通过滑动窗口机制实现流量控制,即接收方通过调整窗口大小来控制发送方的发送速率。 拥塞控制则通过拥塞窗口和拥塞避免算法来控制网络的拥塞程度,保证网络的稳定性和公平性。 5. 可靠性保证TCP协议通过序号、确认号、超时重传、选择性重传等机制来保证数据的可靠传输。 发送方在发送数据后等待接收方的确认,如果在一定时间内未收到确认,则进行超时重传。 三、协议工作原理1. 建立连接客户端发送SYN(同步)报文段给服务器,服务器收到后回复SYN+ACK(同步+确认)报文段给客户端,客户端再回复ACK(确认)报文段给服务器,完成连接的建立。 2. 数据传输建立连接后,发送方将应用层数据分成小的数据段,并添加TCP头部信息,然后通过网络传输给接收方。 接收方根据序号和确认号进行数据的重组和排序,再将数据交给应用层。 3. 流量控制与拥塞控制发送方根据接收方发送的窗口大小来控制发送速率,确保不会发送过多的数据导致接收方无法处理。 TCPSend函数的阻塞和⾮阻塞,以及TCP发送数据的异常情况 有了 TCP 协议本⾝的 ACK 机制为什么还需要业务层的ACK 机制?答:这个问题从操作系统(linux/windows/android/ios)实现TCP协议的原理⾓度来说明更合适:1 操作系统在TCP发送端创建了⼀个TCP发送缓冲区,在接收端创建了⼀个TCP接收缓冲区;2 在发送端应⽤层程序调⽤send()⽅法成功后,实际是将数据写⼊了TCP发送缓冲区;3 根据TCP协议的规定,在TCP连接良好的情况下,TCP发送缓冲区的数据是“有序的可靠的”到达TCP接收缓冲区,然后回调接收⽅应⽤层程序来通知数据到达;4 但是在TCP连接断开的时候,在TCP的发送缓冲区和TCP的接收缓冲区中可能还有数据,那么操作系统如何处理呢?⾸先,对于TCP发送缓冲区中还未发送的数据,操作系统不会通知应⽤层程序进⾏处理(试想⼀下:send()函数已经返回成功了,后⾯再告诉你失败,这样的系统如何设计?太复杂了...),通常的处理⼿段就是直接回收TCP发送缓存区及其socket资源;对于TCP接收⽅来说,在还未监测到TCP连接断开的时候,因为TCP接收缓冲区不再写⼊数据了,所以会有⾜够的时间进⾏处理,但若未来得及处理就发现了连接断开,仍然会为了及时释放资源,直接回收TCP接收缓存区和对应的socket资源。 总结⼀下就是:发送⽅的应⽤层程序,调⽤send()⽅法返回成功的时候,数据实际是写⼊到了TCP的发送缓冲区,⽽⾮已经被接收⽅的应⽤层程序处理。 怎么办呢?只能借助于应⽤层的ACK机制。 ----- tcp send函数的阻塞和⾮阻塞 tcp协议本⾝是可靠的,并不等于应⽤程序⽤tcp发送数据就⼀定是可靠的.不管是否阻塞,send发送的⼤⼩,并不代表对端recv到多少的数据. 在阻塞模式下, send函数的过程是将应⽤程序请求发送的数据拷贝到发送缓存中发送就返回.但由于发送缓存的存在,表现为:如果发送缓存⼤⼩⽐请求发送的⼤⼩要⼤,那么send函数⽴即返回,同时向⽹络中发送数据;否则,send会等待接收端对之前发送数据的确认,以便腾出缓存空间容纳新的待发送数据,再返回(接收端协议栈只要将数据收到接收缓存中,就会确认,并不⼀定要等待应⽤程序调⽤recv),如果⼀直没有空间能容纳待发送的数据,则⼀直阻塞; 在⾮阻塞模式下,send函数的过程仅仅是将数据拷贝到协议栈的缓存区⽽已,如果缓存区可⽤空间不够,则尽能⼒的拷贝,⽴即返回成功拷贝的⼤⼩;如缓存区可⽤空间为0,则返回-1,同时设置errno为EAGAIN. ------(⼀)基础知识IPv4 数据报最⼤⼤⼩是65535(16位),包括IPv4头部。 TCP协议的多线程并发设计与实现方法一、引言随着互联网的迅猛发展,网络通信成为了人们生活中不可或缺的一部分。 而TCP协议作为一种可靠的传输协议,在保证数据完整性和可靠性方面具有显著优势。 然而,随着用户数量的增多,单线程的TCP 协议已经无法满足对并发处理的需求。 因此,本文将讨论TCP协议的多线程并发设计与实现方法。 二、多线程并发设计为了实现TCP协议的多线程并发,需要设计合理的线程模型。 一般而言,可以采用线程池的方式实现。 线程池中包含一个任务队列和一组工作线程,当有新的连接请求到达时,任务队列会派发任务给工作线程进行处理,从而实现并发处理。 三、多线程并发实现方法在实现多线程并发的同时,需要保证数据的可靠性和一致性。 下面将介绍两种常用的方法。 1. 多线程互斥锁一种常见的多线程并发实现方法是采用互斥锁来保护共享资源。 当多个线程同时访问同一个共享资源时,通过加锁的方式来保证同一时间只有一个线程可以对资源进行访问。 这样可以避免多个线程同时修改同一个资源导致数据不一致的问题。 然而,互斥锁的使用也会带来一定的性能开销,因为在多个线程之间频繁加锁和解锁会引起上下文切换。 2. 多线程条件变量另一种多线程并发的实现方法是采用条件变量。 条件变量允许线程在某个条件满足时等待,从而避免了频繁的忙等待。 当某个条件满足时,线程可以被唤醒并开始执行。 这种方法可以有效地减少线程之间的竞争和上下文切换的开销,提高并发处理的效率。 四、多线程并发的挑战和应对策略虽然多线程并发设计和实现可以提高TCP协议的处理能力,但也面临一些挑战。 1. 线程安全多线程并发需要考虑线程安全的问题。 在设计和编码时,需要合理地处理线程之间的同步和竞争条件,以避免数据不一致和资源竞争的问题。 2. 死锁和饥饿多线程并发可能导致死锁和饥饿的问题。 死锁是指线程间相互等待对方释放资源导致无法继续进行的情况,饥饿则是指某些线程长时间无法获取资源的情况。 为了避免这些问题,需要合理地设计线程间的依赖关系和资源分配策略。 TCP传输通讯协议 概述 TCP(Transmission Control Protocol)是一种在网络中用于数据传输的通讯协议。它是基于IP(Internet Protocol)协议的一种高级协议,负责保证数据的可靠性、有序性和完整性。TCP在互联网中广泛应用,被认为是面向连接的可靠数据传输协议。 本文将详细介绍TCP传输通讯协议的工作原理、特点和应用场景。 工作原理 TCP通讯协议工作在网络层和传输层之间,它的主要任务是将应用层传递下来的数据流拆分成适合网络传输的数据包,并在接收端重新组装成完整的数据流。 TCP的核心机制是面向连接的,通信过程中需要建立连接、发送数据和释放连接。在建立连接阶段,客户端与服务端之间进行三次握手,确保双方都准备好进行通信。一旦连接建立,双方可以开始传输数据。发送方将数据切分成适当大小的数据段,并给每个数据段分配一个序号。接收方通过确认应答的方式告知发送方哪些数据已经接收。发送方根据接收方的确认应答情况进行数据的重传或者丢弃。最后,在释放连接阶段,双方发送FIN报文进行连接的结束。 特点 可靠性 TCP协议通过使用序号、确认应答和重传等机制来保证数据的可靠性传输。每个数据段都带有一个唯一的序号,在接收方收到数据后,会发送一个确认应答,告知发送方数据已经正确接收。如果发送方在一定时间内没有收到确认应答,会自动重传数据。 有序性 TCP协议保证数据的有序性传输。每个数据段都有一个序号,接收方通过序号来确定数据的正确顺序。如果接收方收到了乱序的数据段,会先缓存,等待之前的数据段到达后再按正确的顺序组装。 流量控制 TCP协议通过使用滑动窗口机制来控制数据的发送速率,以避免发送方发送速度过快而导致接收方无法处理的问题。接收方可以动态调整滑动窗口的大小,告知发送方自己的接收能力。发送方根据接收方的能力来发送数据,保证不会发送过多的数据导致接收方缓冲区溢出。 拥塞控制 TCP协议通过使用慢启动、拥塞避免和拥塞发生时的恢复机制,来控制网络拥塞情况下的数据传输速率。TCP会根据网络的拥塞程度动态调整自己的发送速率,以避免造成网络拥塞的恶性循环。 应用场景 TCP协议广泛应用于各种需要可靠数据传输的场景。以下是一些常见的应用场景: TCP协议传输机制TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的传输协议,通过字节流方式在网络中传输数据。 它是基于IP(Internet Protocol,互联网协议)协议构建的,提供了传输层的功能。 1.连接管理:TCP连接是在两个主机之间建立,通信前需要进行三次握手。 首先,客户端主动向服务器发送SYN(同步序列编号)包,请求建立连接。 服务器接收到SYN包后,向客户端回复ACK(确认编号)和SYN包,表示接收到请求,并且同意建立连接。 最后,客户端再次向服务器发送ACK包,表示已收到服务器的回复,连接建立成功。 握手过程中,双方会约定序列号,用于对数据进行排序和确认。 2.数据传输:数据传输过程中,TCP会将数据分割成多个报文段,每个报文段包含序列号、确认号和数据等信息。 发送方将报文段通过IP协议发送到目标主机,接收方收到报文段后,根据序列号进行排序和重组,再发送ACK确认报文段给发送方。 如果发送方未收到ACK确认或者收到重复的ACK确认,就会进行重传。 这种机制保证了数据的可靠传输,即使在网络中有丢包或者出错情况下,TCP也可以通过超时重传和确认机制进行恢复。 3.拥塞控制:TCP还具有拥塞控制的功能,它可以根据网络的拥塞程度和带宽情况动态地调整发送速率。 当网络中有拥塞发生时,TCP会降低发送速率,以避免造成更严重的拥塞。 TCP中有一种拥塞窗口(Congestion Window)机制,通过控制发送方的窗口大小来实现。 发送方每次发送数据后,会等待接收方的ACK确认,如果接收方的反馈速度较慢,表明网络出现拥塞,发送方会减小窗口大小,降低发送速率。 反之,如果网络状况良好,发送方会逐渐增大窗口,提高发送速率。 通过这种机制,TCP可以根据网络情况自适应地调整发送速率,使得整个网络的负载合理分配。 总之,TCP协议通过连接管理、数据传输和拥塞控制等机制,实现了可靠的数据传输。 使用TCP协议实现文件传输2013-01-18 10:35:43 我来说两句作者:hanchaoqi收藏我要投稿使用TCP协议实现文件传输。 程序会分为服务器端和客户端,首先运行服务器端,监听来自客户端的连接,客户端运行后会通过程序内的服务器端IP地址,向服务器发送连接请求。 双方建立请求之后,客户端将所需文件的文件名和绝对路径传输给服务器,如果服务器找到此文件,则将此文件传输给客户端,然后断开连接。 具体算法描述如下:【1】服务器端:1、初始化socket服务2、监听连接请求并做相应的处理2.1创建监听套接字2.2监听套接口2.3接受套接字的连接2.4接收客户端传来的数据case 文件绝对路径:按照路径找到文件,并打开。 提取本地文件名,发回给客户端发送文件总长度给客户端case 已准备接收文件完毕if 发送缓冲区为空读取文件,写入缓冲区将文件流分成大小相同的组(最后一组可能会小一点),顺次发送给客户端将缓冲区清空case 文件成功传送打印消息,退出case 文件已存在打印消息,退出2.5关闭同客户端的连接3、释放socket服务【2】客户端:1、初始化socket,winsock服务2、连接服务器,进行数据的传输2.1初始化,创建套接字2.2通过IP地址,向服务器发送连接请求,建立连接2.3主动发送所求文件绝对路径2.4接受服务器端数据并做相应处理case 打开文件错误:重新发送文件绝对路径至服务器,请求重发case 文件长度:打印消息case 文件名:if 文件已经存在发送“文件已经存在”else分配缓冲区,并向服务器发送“Ready”消息case 文件流:为已接收文件名创建文件打开文件,将文件流数据写入文件,直至接收所有分组数据发送“成功接收“消息3、关闭套接字释放服务源程序:【1】服务器端:头文件:[cpp]/*server.h*/#pragma comment(lib, "WS2_32")#include <WinSock2.h>#include <iostream>#include <assert.h>#include<Windows.h>#ifndef COMMONDEF_H#define COMMONDEF_H#define MAX_PACKET_SIZE 10240 // 数据包的最大长度,单位是sizeof(char)#define MAXFILEDIRLENGTH 256 // 存放文件路径的最大长度#define PORT 4096 // 端口号//#define SERVER_IP "127.0.0.1" // server端的IP地址// 各种消息的宏定义#define INVALID_MSG -1 // 无效的消息标识#define MSG_FILENAME 1 // 文件的名称#define MSG_FILELENGTH 2 // 传送文件的长度#define MSG_CLIENT_READY 3 // 客户端准备接收文件#define MSG_FILE 4 // 传送文件#define MSG_SENDFILESUCCESS 5 // 传送文件成功#define MSG_OPENFILE_ERROR 10 // 打开文件失败,可能是文件路径错误找不到文件等原因#define MSG_FILEALREADYEXIT_ERROR 11 // 要保存的文件已经存在了class CCSDef{public:#pragma pack(1) // 使结构体的数据按照1字节来对齐,省空间// 消息头struct TMSG_HEADER{char cMsgID; // 消息标识TMSG_HEADER(char MsgID = INVALID_MSG): cMsgID(MsgID){}};// 请求传送的文件名// 客户端传给服务器端的是全路径名称// 服务器传回给客户端的是文件名struct TMSG_FILENAME : public TMSG_HEADER{char szFileName[256]; // 保存文件名的字符数组TMSG_FILENAME(): TMSG_HEADER(MSG_FILENAME){}};// 传送文件长度struct TMSG_FILELENGTH : public TMSG_HEADER{long lLength;TMSG_FILELENGTH(long length): TMSG_HEADER(MSG_FILELENGTH), lLength(length) {}};// Client端已经准备好了,要求Server端开始传送文件struct TMSG_CLIENT_READY : public TMSG_HEADER{TMSG_CLIENT_READY(): TMSG_HEADER(MSG_CLIENT_READY){}};// 传送文件struct TMSG_FILE : public TMSG_HEADER{union // 采用union保证了数据包的大小不大于MAX_PACKET_SIZE * sizeof(char){char szBuff[MAX_PACKET_SIZE];struct{int nStart;int nSize;char szBuff[MAX_PACKET_SIZE - 2 * sizeof(int)]; }tFile;};TMSG_FILE(): TMSG_HEADER(MSG_FILE){}};// 传送文件成功struct TMSG_SENDFILESUCCESS : public TMSG_HEADER {TMSG_SENDFILESUCCESS(): TMSG_HEADER(MSG_SENDFILESUCCESS){}};// 传送出错信息,包括:// MSG_OPENFILE_ERROR:打开文件失败// MSG_FILEALREADYEXIT_ERROR:要保存的文件已经存在了struct TMSG_ERROR_MSG : public TMSG_HEADER{TMSG_ERROR_MSG(char cErrorMsg): TMSG_HEADER(cErrorMsg){}};#pragma pack()};#endifcpp文件:[cpp]/*Server.cpp*/#include"Server.h"char g_szNewFileName[MAXFILEDIRLENGTH];char g_szBuff[MAX_PACKET_SIZE + 1];long g_lLength;char* g_pBuff = NULL;//初始化socket库bool InitSocket();//关闭socket库bool CloseSocket();//解析消息并进行相应的处理bool ProcessMsg(SOCKET sClient);//监听Client消息void ListenToClient();//打开文件bool OpenFile(CCSDef::TMSG_HEADER* pMagHeader,SOCKET sClient); //传送文件bool SendFile(SOCKET sClient);//读取文件进缓冲区bool ReadFile(SOCKET sClient);int main(){while(1){InitSocket();ListenToClient();CloseSocket();system("del E:\\test1.A_exp");}//system("pause");return 0;}//初始化socket库bool InitSocket(){WSADATA wsaData;WORD socketVersion=MAKEWORD(2,2);if(::WSAStartup(socketVersion,&wsaData)!=0){//初始化WinSock服务printf("Init socket dll error\n");return false;}return true;}//关闭socket库bool CloseSocket(){//释放winsock库::WSACleanup();if(g_pBuff != NULL){delete [] g_pBuff;g_pBuff = NULL;}return true;}//解析消息并进行相应的处理bool ProcessMsg(SOCKET sClient){//从套接口中接收数据,返回copy的字节数int nRecv = ::recv(sClient,g_szBuff,MAX_PACKET_SIZE+1,0); if(nRecv>0){g_szBuff[nRecv]='\0';}//解析命令CCSDef::TMSG_HEADER*pMsgHeader=(CCSDef::TMSG_HEADER*)g_szBuff;switch(pMsgHeader->cMsgID){case MSG_FILENAME://文件名{OpenFile(pMsgHeader,sClient);}break;case MSG_CLIENT_READY://客户端已准备完毕,开始传送文件{SendFile(sClient);}break;case MSG_SENDFILESUCCESS://传送文件成功{printf("Send File Success!\n");return false;}break;case MSG_FILEALREADYEXIT_ERROR://要保存的文件已经存在{printf("The file ready to send already exit!\n"); return false;}break;}return true;}//监听Client消息void ListenToClient(){//创建套接字SOCKET sListen = ::socket(AF_INET, SOCK_STREAM,IPPROTO_TCP);if(sListen == SOCKET_ERROR){printf("Init Socket Error!\n");return;}//绑定socketsockaddr_in sin;sin.sin_family=AF_INET;sin.sin_port=htons(PORT);sin.sin_addr.S_un.S_addr=INADDR_ANY;if (::bind(sListen, (LPSOCKADDR)&sin, sizeof(sockaddr_in)) == SOCKET_ERROR){printf("Bind Error!\n");return;}// 设置socket进入监听状态if(::listen(sListen,10)==SOCKET_ERROR){printf("Listen Error!\n");return;}printf("Listening To Client...\n");//循环接收client端的连接请求sockaddr_in ClientAddr;int nAddrLen = sizeof(sockaddr_in);SOCKET sClient;//取队列最前端客户连接请求,创建套接字连接通道while((sClient=::accept(sListen,(sockaddr*)&ClientAddr,&nAd drLen))==INVALID_SOCKET){}//解析消息并进行相应的处理//int count=10;//作为定时当程序执行10s未完成时直接退出//while(ProcessMsg(sClient)==true&&count>0)//{// Sleep(1000);// count--;while(ProcessMsg(sClient)==true){Sleep(1000);}//关闭同客户端的连接::closesocket(sClient);::closesocket(sListen);}//打开文件bool OpenFile(CCSDef::TMSG_HEADER* pMsgHeader,SOCKET sClient) {CCSDef::TMSG_FILENAME*pRequstFileNameMsg=(CCSDef::TMSG_FILENAME*)pMsgHeader;//对文件名进行处理char *p1,*p2;for(p1=pRequstFileNameMsg->szFileName,p2=g_szNewFileName;*p 1!='\0';p1++,p2++){if(*p1!='\n'){*p2=*p1;}if(*p2=='\\')//将‘\’转换为‘\\’{*(++p2)='\\';}}*p2='\0';ReadFile(sClient);return true;}//传送文件bool SendFile(SOCKET sClient){if (NULL == g_pBuff){//如果缓冲区为空ReadFile(sClient);}int nPacketBufferSize = MAX_PACKET_SIZE - 2 * sizeof(int); // 每个数据包存放文件的buffer大小// 如果文件的长度大于每个数据包所能传送的buffer长度那么就分块传送for (int i = 0; i < g_lLength; i += nPacketBufferSize)CCSDef::TMSG_FILE tMsgFile;tMsgFile.tFile.nStart = i;if (i + nPacketBufferSize + 1> g_lLength){//文件块已经是最后一块tMsgFile.tFile.nSize = g_lLength - i;}else{tMsgFile.tFile.nSize = nPacketBufferSize;}memcpy(tMsgFile.tFile.szBuff, g_pBuff +tMsgFile.tFile.nStart, tMsgFile.tFile.nSize);//copy到缓冲区::send(sClient, (char*)(&tMsgFile),sizeof(CCSDef::TMSG_FILE), 0);Sleep(0.5);}delete [] g_pBuff;g_pBuff = NULL;return true;}//读取文件进缓冲区bool ReadFile(SOCKET sClient){if(g_pBuff!=NULL){//如果缓冲区不为空return true;}//打开文件FILE *pFile;if((pFile = fopen(g_szNewFileName, "rb"))==NULL){//文件打开失败,发送错误报告printf("Cannot find the file, request the client input file name again\n");CCSDef::TMSG_ERROR_MSGtMsgErrorMsg(MSG_OPENFILE_ERROR);::send(sClient, (char*)(&tMsgErrorMsg),sizeof(CCSDef::TMSG_ERROR_MSG), 0);return false;}//传送文件长度到Clientfseek(pFile,0,SEEK_END);//重定位指针到文件末尾g_lLength=ftell(pFile);//返回文件指针相对于文件头的偏移量printf("File Length = %d\n", g_lLength);CCSDef::TMSG_FILELENGTH tMsgFileLength(g_lLength);::send(sClient,(char*)(&tMsgFileLength),sizeof(CCSDef::TMSG_FILELENGTH), 0);// 处理文件全路径名,把文件名分解出来//磁盘号,目录,文件名,后缀名char szDrive[_MAX_DRIVE], szDir[_MAX_DIR],szFname[_MAX_FNAME], szExt[_MAX_EXT];_splitpath(g_szNewFileName, szDrive, szDir, szFname, szExt);strcat(szFname,szExt);CCSDef::TMSG_FILENAME tMsgFileName;strcpy(tMsgFileName.szFileName, szFname);printf("Send File Name: %s\n", tMsgFileName.szFileName); ::send(sClient, (char*)(&tMsgFileName),sizeof(CCSDef::TMSG_FILENAME), 0);//分配缓冲区,读取文件内容g_pBuff = new char[g_lLength + 1];if (g_pBuff == NULL){return false;}fseek(pFile, 0, SEEK_SET);fread(g_pBuff, sizeof(char), g_lLength, pFile);g_pBuff[g_lLength] = '\0';fclose(pFile);return true;}【2】客户端:头文件同服务器端头文件源程序文件:[cpp]/*Client.cpp*/#include"Client.h"long g_lLength = 0;char* g_pBuff = NULL;char g_szFileName[MAXFILEDIRLENGTH];char g_szBuff[MAX_PACKET_SIZE+1];SOCKET g_sClient;// 初始化socket库bool InitSocket();// 关闭socket库bool CloseSocket();// 把用户输入的文件路径传送到server端bool SendFileNameToServer();// 与server端连接bool ConectToServer();// 打开文件失败bool OpenFileError(CCSDef::TMSG_HEADER *pMsgHeader);// 分配空间以便写入文件bool AllocateMemoryForFile(CCSDef::TMSG_HEADER *pMsgHeader); // 写入文件bool WriteToFile(CCSDef::TMSG_HEADER *pMsgHeader);// 处理server端传送过来的消息bool ProcessMsg();int main(){while(1){InitSocket();ConectToServer();CloseSocket();}//system("pause");return 0;}// 初始化socket库bool InitSocket(){//初始化SOCKETWSADATA wsaData;WORD socketVersion=MAKEWORD(2,2);if(::WSAStartup(socketVersion,&wsaData)!=0){printf("Init socket dll error\n");exit(-1);}return true;}// 关闭socket库bool CloseSocket(){// 关闭套接字::closesocket(g_sClient);// 释放winsock库::WSACleanup();return true;}// 把用户输入的文件路径传送到server端bool SendFileNameToServer(){char szFileName[MAXFILEDIRLENGTH];printf("Input the File Directory: ");//fgets(szFileName, MAXFILEDIRLENGTH, stdin);strcpy(szFileName,"E:\\test1.A_exp");// 把文件路径发到server端CCSDef::TMSG_FILENAME tMsgRequestFileName;strcpy(tMsgRequestFileName.szFileName, szFileName);if (::send(g_sClient, (char*)(&tMsgRequestFileName),sizeof(CCSDef::TMSG_FILENAME), 0) == SOCKET_ERROR){printf("Send File Name Error!\n");exit(-1);}return true;}// 与server端连接bool ConectToServer(){// 初始化socket套接字if ((g_sClient = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == SOCKET_ERROR){printf("Init Socket Error!\n");exit(-1);}sockaddr_in servAddr;servAddr.sin_family = AF_INET;servAddr.sin_port = htons(PORT);servAddr.sin_addr.S_un.S_addr = ::inet_addr(SERVER_IP); if ((::connect(g_sClient, (sockaddr*)&servAddr,sizeof(sockaddr_in))) == INVALID_SOCKET){printf("Connect to Server Error!\n");exit(-1);}// 输入文件路径传输到server端SendFileNameToServer();// 接收server端传过来的信息,直到保存文件成功为止while (ProcessMsg() == true){Sleep(1000);}return true;}// 打开文件失败bool OpenFileError(CCSDef::TMSG_HEADER *pMsgHeader){if (g_pBuff != NULL)//如果缓冲区内有数据return true;assert(pMsgHeader != NULL);printf("Cannot find file!\n");// 重新输入文件名称SendFileNameToServer();return true;}// 分配空间以便写入文件bool AllocateMemoryForFile(CCSDef::TMSG_HEADER *pMsgHeader) {assert(pMsgHeader != NULL);if (g_pBuff != NULL){return true;}CCSDef::TMSG_FILENAME* pRequestFilenameMsg = (CCSDef::TMSG_FILENAME*)pMsgHeader;printf("File Name: %s\n",pRequestFilenameMsg->szFileName);// 把文件的路径设置为D盘根目录下strcpy(g_szFileName, "D:\\");strcat(g_szFileName, "test2.B_imp");//strcat(g_szFileName, pRequestFilenameMsg->szFileName); // 查找相同文件名的文件是否已经存在,如果存在报错退出FILE* pFile;if ((pFile = fopen(g_szFileName, "r")) != NULL){// 文件已经存在,要求重新输入一个文件printf("The file already exist!\n");CCSDef::TMSG_ERROR_MSGtMsgErrorMsg(MSG_FILEALREADYEXIT_ERROR);::send(g_sClient, (char*)(&tMsgErrorMsg),sizeof(CCSDef::TMSG_ERROR_MSG), 0);fclose(pFile);return false;}// 分配缓冲区开始接收文件,如果分配成功就给server端发送开始传送文件的要求g_pBuff = new char[g_lLength + 1];if (g_pBuff != NULL){memset(g_pBuff, '\0', g_lLength + 1);printf("Now ready to get the file %s!\n", pRequestFilenameMsg->szFileName);CCSDef::TMSG_CLIENT_READY tMsgClientReady;if (::send(g_sClient, (char*)(&tMsgClientReady),sizeof(CCSDef::TMSG_CLIENT_READY), 0) == SOCKET_ERROR){printf("Send Error!\n");exit(-1);}}else{printf("Alloc memory for file error!\n");exit(-1);}return true;}// 写入文件bool WriteToFile(CCSDef::TMSG_HEADER *pMsgHeader){assert(pMsgHeader != NULL);CCSDef::TMSG_FILE* pMsgFile =(CCSDef::TMSG_FILE*)pMsgHeader;int nStart = pMsgFile->tFile.nStart;int nSize = pMsgFile->tFile.nSize;memcpy(g_pBuff + nStart, pMsgFile->tFile.szBuff, nSize); if (nStart == 0){printf("Saving file into buffer...\n");}memcpy(g_pBuff + nStart, pMsgFile->tFile.szBuff, nSize); // 如果已经保存到缓冲区完毕就写入文件if (nStart + nSize >= g_lLength){printf("Writing to disk....\n");// 写入文件FILE* pFile;pFile = fopen(g_szFileName, "w+b");fwrite(g_pBuff, sizeof(char), g_lLength, pFile);delete [] g_pBuff;g_pBuff = NULL;fclose(pFile);// 保存文件成功传送消息给server退出serverCCSDef::TMSG_SENDFILESUCCESS tMsgSendFileSuccess;while (::send(g_sClient, (char*)(&tMsgSendFileSuccess), sizeof(CCSDef::TMSG_SENDFILESUCCESS), 0) == SOCKET_ERROR){}printf("Save the file %s success!\n", g_szFileName); return true;}else{return false;}}// 处理server端传送过来的消息bool ProcessMsg(){CCSDef::TMSG_HEADER *pMsgHeader;int nRecv = ::recv(g_sClient, g_szBuff, MAX_PACKET_SIZE + 1, 0);pMsgHeader = (CCSDef::TMSG_HEADER*)g_szBuff;switch (pMsgHeader->cMsgID){case MSG_OPENFILE_ERROR: // 打开文件错误{OpenFileError(pMsgHeader);}break;case MSG_FILELENGTH: // 文件的长度{if (g_lLength == 0){g_lLength =((CCSDef::TMSG_FILELENGTH*)pMsgHeader)->lLength;printf("File Length: %d\n", g_lLength);}}break;case MSG_FILENAME: // 文件名{return AllocateMemoryForFile(pMsgHeader);}break;case MSG_FILE: // 传送文件,写入文件成功之后退出这个函数{ if (WriteToFile(pMsgHeader)){/*Sleep(1000);*/return false;}}break;}return true;}。 TCP、UDP的阻塞和⾮阻塞模式前⾔socket在默认情况下是阻塞状态的,这就使得发送和接受都处于阻塞状态;TCP协议下发送选⽤send,UDP协议下,发送选⽤sendto.TCP协议下接收选⽤recv,UDP协议下,接收选⽤recvfrom.⼀、阻塞模式&&send假设发送缓冲区⼤⼩为4096KB,其中已经使⽤4000KB空间,剩余96KB空间;现在⼜有200KB的数据需要发送,则只能先将96KB的数据放⼊发送缓冲区但不发送(阻塞),直到缓冲区空出空间⾜够放⼊剩余104KB,再send操作将这200⼦节发送,返回发送成功字节数200;⼆、⾮阻塞&&send与阻塞不同,⾮阻塞模式,发送缓冲区得到数据会⽴即发送,例:当缓冲区只有96KB空间,但要发送200KB数据要进⼊发送缓冲区,则直接调⽤send并返回成功字节数96,并不等待将剩余的104字节放⼊缓冲区同时发送;三、阻塞/阻塞模式&&sendtoUDP的sendto并不能阻塞发送,因为UDP并没有真正的发送缓冲区,它所做的只是将应⽤缓冲区拷贝给下层协议栈,在此过程中加上UDP 头,IP头,所以实际不存在阻塞。 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++四、阻塞模式&&recv将会阻塞到发送缓冲区⾄少 1 个字节,才返回;在没有数据到来时,对它们的调⽤都将处于睡眠状态,不会返回。 五、阻塞模式&& recvfrom将会阻塞到发送缓冲区⾄少⼀个完整的UDP数据报,才返回;在没有数据到来时,对它们的调⽤都将处于睡眠状态,不会返回。 六、阻塞模式 / ⾮阻塞模式&&recv如果缓冲区有任何⼀个字节数据(TCP)或者⼀个完整UDP数据报,它们将会返回接收到的数据⼤⼩。 【知识详解】传输层详解(秋招总结)传输层详解⽬录1.传输层概述1.1 概述TCP⾪属于传输层,所以要⾸先明⽩传输层的作⽤是什么,传输层能够实现端到端的连接。 ⽐如说我们⽤QQ与别⼈发信息,⽹络层能够将信息发送到对⽅的主机上,主机上使⽤什么协议来接受这个信息就由传输层来完成,所以传输层实现的是进程到进程间的连接。 传输层提供的是应⽤程序间的逻辑通信,也就是说它向⾼层(应⽤层)屏蔽了下⾯⽹络层的细节,使应⽤程序看起来好像是在传输层之间沿着⽔平⽅向传输数据,但事实上两者之间并没有这样⼀条实际的物理连接。 1.2 功能1.⽹络层提供了点到点的连接,⽽传输层提供了端到端的服务,也就是进程间的通信;2.⽹络层提供的是不可靠的连接,传输层能够实现可靠的传输;1.3 协议TCP(Transmission Control:Protocol) 传输控制协议UDP(User Datagram Protocol) ⽤户数据报协议1.4 传输层和应⽤层的关系1.4.1 端⼝TCP/IP传输层⽤⼀个16位端⼝号(0~65535)来标识⼀个端⼝,但是注意,端⼝号只具有本地意义,不同计算机的相同端⼝号没有关联,0⼀般不⽤,所以允许有65535个不同的端⼝号。 两个计算机的进程要实现通信,不仅必须知道对⽅的IP地址(为了找到对⽅的计算机),⽽且还要知道对⽅的端⼝号(为了找到对⽅计算机中的应⽤程序)问:怎么理解端⼝?在⽹络技术中,端⼝(Port)⼤致有两种意思:1.硬件端⼝,也就是设备间交互的接⼝,是物理意义上的端⼝,⽐如集线器,交换机等设备的接⼝;2.软件端⼝,指的是应⽤层的的进程和运输层进⾏层间交互的⼀种地址,是逻辑意义上的端⼝,⼀般指的是TCP/IP协议中的端⼝。 正是这种端⼝,所有传输层实现的是端到端的通信;在TCP/IP协议中,⽤"源IP地址、⽬的IP地址、源端⼝号、⽬的端⼝号、协议号"这五部分组成⼀个套接字,来标识⼀次通信;⼀个进程可以绑定多个端⼝号,因为⼀个进程可以有很多线程或者说是⼦进程等,这每⼀个都对应⼀个端⼝号,所以⼀个进程可以绑定多个端⼝号;但是⼀个端⼝号不可以被多个进程绑定,每⼀个端⼝号都与唯⼀的进程对应,if有多个了,那通信不就乱了套了吗;⼀个端⼝号⼀个进程,⼀个进程可以多个端⼝;端⼝号分类公认端⼝:0~1023,明确与某种服务绑定,⽐如各种协议;注册端⼝:1024~65535:松散的绑定⼀些服务,也就是有许多服务绑定这些端⼝。 服务器端: #include "stdafx.h" #include using namespace std; #include #pragma comment(lib,"ws2_32.lib") #include #include #include #define PORT 2000 #define addr_s "127.0.0.1" #define M 1024 typedef struct { char my_type[10]; int size; char buff[M]; }my_data; int n; typedef list list_data; //list_data accept_list[20]={}; //int size = 0; typedef struct { SOCKET accept_fd; list_data accept_list; }accept_data; DWORD WINAPI Send_proc(LPVOID p_data); DWORD WINAPI myread_proc(LPVOID p_data); DWORD WINAPI my_accept_proc(LPVOID p_data); fd_set fdr_w; timeval tv; class Run_thread { public: void Init_Socket(); void tcp_server_ready(); static void Create_thread(); private: fd_set fdr_w; timeval tv; int retval; SOCKET fd; }; DWORD WINAPI Send_proc(LPVOID p_data) { Run_thread send_thread; send_thread.Init_Socket(); send_thread.tcp_server_ready(); return 0; } DWORD WINAPI myaccept_proc(LPVOID p_data) { HANDLE hThread_read; SOCKET fd = *((SOCKET*)p_data); printf("用户已经连接,准备发送数据\n"); list_data my_list; hThread_read=CreateThread(NULL,0,myread_proc,(LPVOID)&my_list,0,NULL); CloseHandle(hThread_read); Sleep(10); printf("开始发送时链表的大小为size= %d",my_list.size()); while(1) { FD_ZERO(&fdr_w); FD_SET(fd,&fdr_w); tv.tv_sec = 10; tv.tv_usec = 0; // printf("链表的大小为:size=%d",my_list.size()); if (select(0,NULL,&fdr_w,NULL,&tv)>0&&FD_ISSET(fd,&fdr_w)&&my_list.size()) { // 一次性发送完 一包数据 int totalsendlen = 0; printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"); printf("链表的size=%d",my_list.size()); while (totalsendlen{ printf("发送一次数据\n"); int sendedlen=send(fd,(char*)(&(*my_list.begin())+totalsendlen),sizeof(*(my_list.begin()))-totalsendlen,0); if (sendedlen==SOCKET_ERROR) { int nerrcode = WSAGetLastError(); /*if (nerrcode == WSAENOBUFS) { Sleep(10); }*/ if (nerrcode == WSAETIMEDOUT) perror("发送超时"),exit(0);//超时 else perror("发送错误"),exit(-1);//错误; } else totalsendlen += sendedlen; } my_list.erase(my_list.begin()); printf("my_list.size()=%d\n",my_list.size()); } } return 0; } DWORD WINAPI myread_proc(LPVOID p_data) { printf("读线程已经启动\n"); FILE* fp; if ((fp=fopen("D://Me.zip","rb"))==NULL) { printf("打开文件失败!\n"); exit(-1); } fseek(fp,0L,2); long filelength=ftell(fp); printf("文件大小是fp=%d\n",filelength); //算出文件包的个数 if (0==filelength%M) { n=filelength/M; printf("预计发送次数n=%d\n",n); } else { n=filelength/M+1; printf("预计发送次数n=%d\n",n); } fseek(fp,0L,0); my_data new_data; //list_data my_list = (*(list_data*)p_data); // list_data my_list =accept_list[size]; while (1) { //初始化结构体 memset(new_data.my_type,0,sizeof(new_data.my_type)); strcpy(new_data.my_type,"song"); memset(new_data.buff,0,M); //往链表后添加元素 if((*(list_data*)p_data).size()<20) { new_data.size=fread(new_data.buff,1,M,fp); (*(list_data*)p_data).push_back(new_data); printf("读入链表后链表的大小为my_list.size()=%d\n",(*(list_data*)p_data).size()); printf("new_data.size=%d\n",new_data.size); --n; } else { Sleep(3); } if (!new_data.size) { printf("new_data.size=%d\n",new_data.size); break; } } Sleep(10); fclose(fp); printf("文件读取成功!\n"); return 0; } void Run_thread::Create_thread() { HANDLE hThread1; hThread1 =CreateThread(NULL,2048,Send_proc,NULL,0,NULL); CloseHandle(hThread1); printf("线程创建成功\n"); } void Run_thread::Init_Socket() { WSADATA WSAData; if(WSAStartup(MAKEWORD(2,2),&WSAData)) { printf("初始化socket失败\n"); exit(-1); } if(LOBYTE(WSAData.wVersion)!=2||HIBYTE(WSAData.wVersion)!=2) { WSACleanup(); exit(-1); } } void Run_thread::tcp_server_ready() { SOCKET sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd==-1)perror("socket"),exit(-1); //准备通信地址 SOCKADDR_IN server; server.sin_family = AF_INET; server.sin_port = htons(PORT); server.sin_addr.S_un.S_addr = inet_addr(addr_s); //绑定 int res = bind(sockfd,(SOCKADDR*)&server,sizeof(server)); if (res==-1)perror("bind"),exit(-1); printf("bind ok\n"); listen(sockfd,5); SOCKADDR_IN from; int len = sizeof(SOCKADDR_IN); while (1) { /*FD_ZERO(&fdr_w); FD_SET(sockfd,&fdr_w); tv.tv_sec = 2; tv.tv_usec = 0; if (select(sockfd + 1,&fdr_w,NULL,NULL,&tv)>0 && FD_ISSET(sockfd,&fdr_w)) {*/ fd = accept(sockfd,(SOCKADDR*)&from,&len); printf("accept返回的fd的大小是%d \n",fd); if (-1==fd) { printf("连接错误\n"); continue; } else { printf("acept ok!\n"); } //} //每个客户端连接后创建一个新的线程 printf("accept返回的fd的大小是%d \n",fd);合集下载
tcp服务器端使用多线程技术同时与多个客户通信的编程方法
网络协议知识:SCTP协议和TCP协议的应用场景和优缺点
TCP协议详解
TCPSend函数的阻塞和非阻塞,以及TCP发送数据的异常情况
TCP协议的多线程并发设计与实现方法(五)
tcp传输通讯协议
TCP协议传输机制
使用TCP协议实现文件传输
TCP、UDP的阻塞和非阻塞模式
【知识详解】传输层详解(秋招总结)
文档推荐