一。Socket异常信息

之所以把对异常信息的介绍放到原理之前讲,是因为由于socket本身的复杂性,导致了产生各种异常的复杂性。我们应该时刻铭记的是,sokcet本身属于系统(OS),是系统对TCP/IP的实现,也就是说,socket发出的异常信息不代表程序出错,甚至不代表系统出错,而仅仅就是代表socket本身的各种异常情况。另外一点我觉得应该强调的是:socket不是TCP/IP;TCP/IP也不是socket。socket是为广泛的协议设计的,涉及TCP/IP的内容只是socket体系中一个很小的子集;而TCP/IP就更加独立于sokcet而存在——TCP/IP是协议描述;socket是对协议理论的一种实现形式。
因为socket是属于系统的,所以不同的系统对于socket有着大同小异的解释,出错描述也不尽相同。在Linux中,socket的异常信息可以通过errno获得(int类型),然后可以通过函数strerror()将int转换成字符串描述;也可以通过函数perror()直接获得其描述。
要使用errno需要包含头文件<errno.h>。我建议使用errno获得int类的错误信息的一个重要原因在于,socket的异常不一定就必然导致程序终止。Bjarne Stroustrup在介绍C++异常机制的时候对C风格的异常机制有着这样的描述:(C++对于异常)的默认响应方式是终止程序。传统的反应(对于发生异常的时候)则是装糊涂,接着做下去,以期得到最好的结果(《C++程序设计语言》第14章 异常处理)。不过以我目前的水平看来,终止正在进行的程序然后再通过异常机制重新启动一个新的流程,其代价远远大于“装糊涂”的让程序继续运行下去,只要错误不是致命的,通过简单的判断和处理或许效果更佳。
例如,socket中就有一个很有代表性的情况,在TCP连接中,如果一方意外退出——也就是说没有通过TCP退出流程退出,比如没有运行完程序关闭掉socket而直接X掉或者Ctrl+c了。socket往往会因为recv()返回值小于0而抛出一个异常。正常断开连接的时候,recv()会通过返回0表示连接已经断开,但是大多数时候,我们并不希望因为异常的断开就导致另外一端的程序终止(想象一下如果你关掉QQ腾讯的服务器程序就终止是什么概念……),所以我们必须处理这种情况。
在Linux中,远程连接异常断开(被重置)的errno代码是104,类似的,我们应该保证出现这种异常的时候程序可以继续运行。

// Filename: SockClass.hpp

#ifndef SOCK_CLASS_HPP
#define SOCK_CLASS_HPP

#include  < unistd.h >
#include  < iostream >
#include  < sys / socket.h >
#include  < arpa / inet.h >
#include  < errno.h >

namespace sockClass
{
void error_info( const char * s);
}

以上是头文件中的声明,下面是函数,我们这里仅仅演示处理了104错误。

namespace sockClass
{
void error_info( const char * s)
{
     int err_info  = errno;
    std::cerr  << strerror(err_info)  << " : errno:  " << err_info  << std::endl;
     if (err_info  == 104 ){
         return ;
    }
    exit( 1 );
}
}

在windows中,错误代码由WSAGetLastError()获得,而无需设置errno。

// Filename: SockClass.hpp

#ifndef SOCK_CLASS_HPP
#define SOCK_CLASS_HPP

#include  < iostream >
#include  < winsock2.h >

namespace sockClass
{
void error_info( const char * s);
}

WinSock的错误代码跟Linux中的不一样,同样的异常,WinSock的错误代码是10054。
并且,由于没有errno也就无从调用strerror(),我们最好自己写出详细的异常信息。
WinSock的详细代码信息在这里:
http://msdn.microsoft.com/en-us/library/ms740668(v=VS.85).aspx
win32下的演示代码如下:

namespace sockClass
{
void error_info( const char * s)
{
     int winsock_err  = WSAGetLastError();
    perror(s);
    std::cerr  << " WinSock Error:  " << winsock_err  << std::endl;
     if (winsock_err  == WSAECONNRESET) {
        std::cerr  << " Connection reset by peer. " << std::endl;
         return ;
    }
    exit( 1 );
}
}
二。实际TCP Socket类
我们在第1节中讲过,socket是一个int的文件描述符(WinSock中直接是一种抽象的描述符),我们通过对这个描述符发出指令操作socket。这是C语言的思想,在面向对象的思想中,最好socket本身是一种对象,各种方法由对象本身发出。用面向对象的思想封装socket并不困难,而且,对于描述socket的概念可能更加直观,这一节,我们边介绍socket和TCP的概念边对socket进行OO封装。
首先,每一个socket对象都具有唯一的socket文件描述符,这样可以很好的对应socket的概念。所以我们构建一个基类,并让其成为纯虚函数——这是因为socket文件描述符必须在具体的构造中才能出现,然后仍然保留一个返回原始的socket文件描述符的接口,这是为了不方便归结到类函数中的函数所预留准备的,比如极其重要的select()我们会在后面讲到,所谓有备无患。

class BaseSock{
protected :
     int sockFD;
public :
    BaseSock();
     virtual ~ BaseSock()  = 0 ;
     const int & showSockFD()  const ;
};

函数实现:

// class BaseSock

BaseSock::BaseSock():
sockFD( - 1 )
{}

BaseSock:: ~ BaseSock()
{}

const int & BaseSock::showSockFD()  const
{
     return sockFD;
}

我们把sockFD的初始值设置为-1,表明在没有派生类构造的时候这是一个非法的文件描述符号(File Descriptor)。
接下来,我们简单回顾一下第一节对于TCP Server的建立:
首先,我们需要建立一个监听socket,然后激活其监听;
然后,在client端连接信息过来之后,通过监听端口将客户端的信息传递给新的socket,从而建立通讯socket。
我们先构建listen socket:

class TCPListenSock:  public BaseSock{
private :
    sockaddr_in listenSockAddr;
public :
     explicit TCPListenSock(unsigned  short listen_port);
     ~ TCPListenSock();
     void TCPListen(
         int max_connection_requests  = 10 )  const ;
};

TCPListenSock建立的目的的就是被动的等待client端寻找握手的connect(),从而收集client端的sock地址信息(包含了IP地址和端口号),然后在需要的时候传递给新的socket建立通讯socket。

TCPListenSock::TCPListenSock(unsigned  short listen_port)
{
    sockFD  = socket(PF_INET,
                    SOCK_STREAM,
                    IPPROTO_TCP);
     if (sockFD  < 0 ) {
        sockClass::error_info( " socket() failed. " );
    }
    memset( & listenSockAddr,  0 ,  sizeof (listenSockAddr));
    listenSockAddr.sin_family  = AF_INET;
    listenSockAddr.sin_addr.s_addr  = htonl(INADDR_ANY);
    listenSockAddr.sin_port  = htons(listen_port);
     if (bind(    sockFD,
                (sockaddr * ) & listenSockAddr,
                 sizeof (listenSockAddr))  < 0 ) {
        sockClass::error_info( " bind() failed. " );
    }
}

TCPListenSock:: ~ TCPListenSock()
{
    close(sockFD);
}

TCPListenSock通过调用socket()建立sockFD;通过指定端口好指明监听端口,这是为客户端能够找到这个端口所必须的。而IP地址设置为INADDR_ANY,其实就是0,这意味着可以是任何一个server端所拥有的IP。TCPListenSock通过bind()将sockFD和SockAddr绑定在一起。这个sockFD只有本机的SockAddr意味着:1、无法建立连接,只有接受数据报;2、只能接受信息,因为没有远程目的地的SockAddr而无法发出信息。
而这对于TPC建立连接的过程来说,既是足够的,也是必须的。事实上,client端发出的第一个握手数据报就被这个sockFD所接收,而返回给client的握手应答和对client的握手请求则由新的sockFD发出。
listen()是将TCPListenSock激活为监听状态,如果不激活,那么任何握手的连接请求都将被这个sockFD所忽略。

void TCPListenSock::TCPListen(
                         int max_connection_requests)  const
{
     if (listen(    sockFD,
                max_connection_requests)  < 0 ) {
        sockClass::error_info( " listen() failed. " );
    }
}
这个函数看来似乎有些多此一举,因为这个监听是可以整合到构造函数中的,也就是说,我们可以一旦建立TCPListenSock就令其激活,事实上这正是SDL_net中的做法,也是让我感到不严谨的地方,因为监听本身是socket的一个概念。
当激活监听的TCPListenSock等待远程client的connect()握手请求的时候,是调用了accept()并且产生阻塞(默认情况下),如果accept()成功返回意味着conect()握手请求请求成功,这时候就通过accept()产生了一个新的sockFD用于TCP通讯。我们把这个新的sockFD构建为TCPServerSock类:

class TCPServerSock:  public BaseSock{
private :
    sockaddr_in clientSockAddr;
protected :
     char * preBuffer;
     int preBufferSize;
    mutable  int preReceivedLength;
public :
     explicit TCPServerSock(
         const TCPListenSock & listen_sock,
         int pre_buffer_size  = 32 );
     virtual ~ TCPServerSock();
     int TCPReceive()  const ;
     int TCPSend( const char * send_data,
             const int & data_length)  const ;
};

这里,我们为TCPServerSock预留一个缓存,这个缓存并不是必须的,但是设置这样一个缓存至少有两个好处:
1、可以在使用时不必专门为recv()建立缓存;
2、类方法TCPReceive()和TCPSend()可以共享这个缓存,在处理很多问题时候很方便,比如echo,就不需要先把recv()的缓存读出来再由send()来发送。
将缓存已用长度preReceiveLength加上关键字mutable表示我们不关心这个长度会被更改,我们只在乎有一个缓存可以用,但是实际用了多少不重要,这样我们就可以为接受和发送的类方法加上const。
我们回到TCPServerSock的建立,TCPServerSock通过TCPListenSock accept()一个远程的client connect()握手请求而建立,所以,TCPServerSock的构造在默认情况下是阻塞的。

TCPServerSock::TCPServerSock(
                 const TCPListenSock & listen_sock,
                 int pre_buffer_size):
preBufferSize(pre_buffer_size),
preReceivedLength( 0 )
{
    preBuffer  = new char [preBufferSize];

socklen_t clientSockAddrLen  = sizeof (clientSockAddr);
    sockFD  = accept(    listen_sock.showSockFD(),
                        (sockaddr * ) & clientSockAddr,
                         & clientSockAddrLen);
     if (sockFD  < 0 ) {
        sockClass::error_info( " accept() failed. " );
    }
    std::cout     << " Client (IP:  "
                 << inet_ntoa(clientSockAddr.sin_addr)
                 << " ) conneted. " << std::endl;
}

TCPServerSock:: ~ TCPServerSock()
{
    delete [] preBuffer;
    close(sockFD);
}

这里需要注意一个Linux和Windows下的不同:
对于sockaddr_in(也包括sockaddr)的大小,被accept()指定的时候,Linux中用的是socklen_t,其实这就是size_t,也就是unsigned int。而WinSock中却用的是int。因为在编译中不会自动转换,所以会提示错误。
再次强调,TCPServerSock的sockFD是通过accept()建立的而不是socket(),这也是唯一一个不用socket()建立的sockFD(包括UDP的)。在client发出的connect()握手请求的数据报中,同时包含着client端的地址信息(IP地址和端口)和server端的地址信息(IP地址和端口),正是这个握手请求数据报中的两边的地址信息通过accept()被传递到TCPServerSock的sockFD中。请注意,server端的信息并非由TCPListenSock提供,因为TCPListenSock中listenSockAddr的IP地址为空(INADDR_ANY == 0),而TCPServerSock中server端的SockAddr却是具体的,由客户端的握手协议传来的(但是没有具体的体现出来)。只有具体的地址(IP地址和端口)才能提供IP数据包的目的地方向。而端口号,则因为client事先知道监听端口号,从而在握手请求中包含,最终传递给TCPListenSock中server端的SockAddr,虽然这个过程决定了这个端口号等于监听端口号,但是需要明白的是,这个端口号来自握手请求的数据报而不是TCPListenSock的listenSockAddr。
新的sockFD具有来向(本机)和去向(远程)的信息,所以可以收发数据。TCPServerSock的sockFD一旦建立,马上向远程返回一个数据报,这个数据报有两层意义:
1、表示server已经接收了client的握手请求;
2、对client发出与server这个新sockFD握手的请求。
这就是所谓第二次握手,并且也是以数据报的形式传送的。我们说过,TCP协议的目标是建立“可靠”的数据流形式的通讯,在这个数据流的通道建立起来以前,只能采用数据报的形式传送数据。
在另外一边的客户端,我们分析一下TCPClientSock的建立过程。

class TCPClientSock:  public BaseSock{
private :
    sockaddr_in serverSockAddr;
protected :
     char * preBuffer;
     int preBufferSize;
    mutable  int preReceivedLength;
public :
    TCPClientSock(
         const char * server_IP,
        unsigned  short server_port,
         int pre_buffer_size  = 32 );
     virtual ~ TCPClientSock();
     int TCPReceive()  const ;
     int TCPSend( const char * send_data,
             const int & data_length)  const ;
};

我们看到TCPClientSock的类与TCPServerSock很类似,构造函数的差别是,TCPClientSock需要提供server端的IP地址和端口号。

TCPClientSock::TCPClientSock(
                     const char * server_IP,
                    unsigned  short server_port,
                     int pre_buffer_size):
preBufferSize(pre_buffer_size),
preReceivedLength( 0 )
{
    preBuffer  = new char [preBufferSize];

sockFD  = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
     if (sockFD  < 0 ) {
        sockClass::error_info( " sock() failed. " );
    }

memset( & serverSockAddr,  0 ,  sizeof (serverSockAddr));
    serverSockAddr.sin_family  = AF_INET;
    serverSockAddr.sin_addr.s_addr  = inet_addr(server_IP);
    serverSockAddr.sin_port  = htons(server_port);

if (connect(sockFD,
                ( struct sockaddr * ) & serverSockAddr,
                 sizeof (serverSockAddr))  < 0 ) {
        sockClass::error_info( " connect() failed. " );
    }
}

TCPClientSock:: ~ TCPClientSock()
{
    delete [] preBuffer;
    close(sockFD);
}

TCPClientSock通过socket()建立起sockFD,然后指定服务器的serverSockAddr,然后通过connect()向serverSockAddr指定的服务器发出握手请求。需要说明的是,调用connect()的时候,系统会检查TCPClientSock的sockFD是否已经绑定了本机的SockAddr,事实上我们也可以通过bind()将本机的IP和指定的端口号绑定在这个sockFD上,但是我们并不关心这个IP地址和端口号(况且很多主机并没有公网IP,特别在中国),所以通常我们不自己去绑定,这样系统就会帮我们完成绑定工作,分配一个空闲的端口号作为本机地址的端口号。
这样TCPClientSock具有来向(本机地址,通常由系统自动完成绑定,也可以指定)和去向(指定的server端地址)的地址信息,所以可以收发信息。于是,TCPClientSock发出的第一个数据报是发给server监听socket的握手请求数据报,TCPListenSock接收这个数据报后,将相关信息传递给TCPServerSock建立新的sockFD,我们上一节讲到,这个新的sockFD建立起来之后马上就向client端返回一个数据报:一方面表示接受第一次握手请求,另外一方面发出第二次握手请求。
收到第二次握手请求后,connect()才会返回,不然就会阻塞,非常“尽力”的去连接server。这个“尽力”的程度跟系统有关,在我的试验中,windows下很快,就几秒;而Debian则接近6分钟!
connect()返回的同时,向server发出了第三次握手的信息,这个信息是对第二次握手请求的认可。所以,第一次和第二次握手包含着连接的请求;而第二次和第三次握手则包含着对握手请求的认可,他们都是在告诉对方:我知道并同意你连接上我了。
至此,TCP三次握手的概念在socket中完整的实现,建立起数据流的TCP通信通道。
三。TCP三次握手
前面3个小节介绍了socket机制对TCP协议三次握手的实现,需要强调的是,与协议独立于实现类似,TCP的三次握手是独立于socket体系的理论。在TCP协议中,三次握手是通过3个TCP格式的IP数据报来实现的。TCP格式的IP数据报中包含着TCP首部,TCP首部信息中包含着对每一个数据报具体内容的描述。我们这里需要介绍的首部位(bit)标志只有3个:
SYN:同步序号用来发起一个连接。因为TCP协议要求数据传送是可靠的,他的实现方式就是对传输的数据的每一个字节(byte)按顺序编号。但是初始序列号(ISN:Initial Sequence Number)并非从0开始,而是一个随时间周而复始变化的32位无符号整数。当一方发起连接的时候,SYN就会被设置成1,同时,在发送的数据部分用一个字节来表明这是一个新连接的开始。因此,假设发起连接的一方的ISN为n,因为SYN会在数据部分添加一个字节表示这是一个新连接的开始,所以这时候的字节序号就成了n+1。
ACK:确认序号有效。TCP协议要求自动检验数据的可靠性,实现方式就是检验字节序号是否正确的衔接。假如接收数据的一方序号已经是m,那么其返回给发送方确认有效的序号就是m+1。一旦连接,ACK始终设置为1,即表示序号有效,并且在所有数据包中总是存在。但是数据是否真的被TCP采用要看序号是否能对应。如果发送方传来的字节序号没有从m+1开始,那么这个IP数据包就不会被采用,返回ACK信息序号依然是m+1;如果发送方传来的字节序号尽管是从m+1开始的,但是在效验时发生了错误,这个数据报依然不会被采用,返回的ACK信息序号依然是m+1。直到接收了通过TCP检验的数据,序号才会继续增加,例如,传来的数据字节序号从m+1开始到m+k结束,并且通过了TCP效验,那么再次传回的ACK信息,序号就成为了m+k+1。
FIN:发送端完成发送。与SYN类似,FIN也会在数据部分占用一个字节,表示这是一个结束符号。
TCP的三次握手过程如下:
1、第一个SYN连接请求由客户端发起,这个数据报将SYN设置为1表示是一个连接请求,并且包含着这次连接的ISN,我们假设其值为n。
2、服务器端收到第一次握手请求的数据报后开始构建反馈的数据报。反馈数据报包括两个部分:第一部分是将连接请求的序号反馈回去,因为SYN本身占了一个字节,所以反馈回去的序号就是n+1;第二部分是自己也向客户端发起SYN连接请求,也将SYN设置为1,并包含这个新连接的ISN,我们设其值为m。
3、客户端回应服务器端的SYN连接请求,将服务器端到客户端连接的序号反馈回去,因为SYN占了一个字节,所以反馈给服务器端的序号是m+1。
由此,我们可以看到,TCP中,客户端到服务器端,服务器端到客户端的连接是分别建立的,具有不同的ISN(n和m),我们在后面可以看到,这也就意味着这两个连接在正常情况下需要分别的断开。
六。字节流的发送与接收
从TCP三次握手的原理我们可以看到,TCP有“保障”的连接实际上可以看做是两个单向的连接:一个通道只负责发送,另外一个只负责接收。并且,传送的信息是以字节为单位保证顺序的。
在socket机制中,应用层的程序以send()函数将数据首先发送到本机系统的发送缓存中,我们称之为SendQ,意指这是一个FIFO(先进先出)的队列。这个缓存是系统决定的,并不是在我们的程序中指定的。然后socket机制负责将SendQ中的数据以字节为单位,按照顺序发送给对方的接收缓存RecvQ中。RecvQ也是一个属于系统的FIFO缓存队列。从程序员的角度看,send()函数只负责把数据送入SendQ,而SendQ何时将数据发送则是不可控的。所以,send()通常不会阻塞,只有在不能立即将数据发送给SendQ的时候才会阻塞,这往往是因为SendQ缓存已满。另外,SendQ并不负责统计每次send()所发送来的字节流的长度,事实上这个长度在TCP中没有意义,因为所有数据都以字节为单位按照FIFO的形式排列在队列中,而并不在乎来自于哪一次的send()。这也就是所谓的TCP无边缘保证,TCP的send()并不在乎每次传送的数据有多少,而只是致力于将数据以字节为单位按照FIFO的形式排列在SendQ队列中。我们看一下TCPServerSock和TCPClientSock的TCPSend()方法:

int TCPServerSock::TCPSend( const char * send_data,
                            const int & data_length)  const
{
     if (data_length  > preBufferSize) {
        sockClass::error_info( " Data is too large, resize preBufferSize. " );
    }

int sent_length  = send(    sockFD,
                            send_data,
                            data_length,
                             0 );
     if (sent_length  < 0 ) {
        sockClass::error_info( " send() failed. " );
    }  else if (sent_length  != data_length) {
        sockClass::error_info( " sent unexpected number of bytes. " );
    }

return sent_length;
}

int TCPClientSock::TCPSend( const char * send_data,
                            const int & data_length)  const
{
     if (data_length  > preBufferSize) {
        sockClass::error_info( " Data is too large, resize preBufferSize. " );
    }

int sent_length  = send(    sockFD,
                            send_data,
                            data_length,
                             0 );
     if (sent_length  < 0 ) {
        sockClass::error_info( " send() failed. " );
    }  else if (sent_length  != data_length) {
        sockClass::error_info( " sent unexpected number of bytes. " );
    }

return sent_length;
}

可以看到,这两个方法除了分属于不同的类名字不一样,其他都是一样的。send()的返回值是实际发送的字节长度。
在收信息的另外一边,当RecvQ没有数据时,recv()就会阻塞(默认情况下),每当有数据可接收,recv()就会返回实际接收到的数据长度。recv()同样不在乎每次接收的数据有多少,其参数只有一个最大长度限制,这个限制是应用程序分配给每次recv()储存数据的缓存大小。所以TCP的send()和recv()不是一一对应的:send()只负责将数据写入本机的SendQ,而recv()只负责把本机RecvQ中的数据读出来。假设send()传送了m+n字节,但是第一次到达远程目的地的RecvQ中只有m字节,于是这里的recv()就会马上返回m字节;剩下的n字节第二次才姗姗来迟,那么就需要第二次调用recv()来接收。

int TCPServerSock::TCPReceive()  const
{
    preReceivedLength  = recv(    sockFD,
                                preBuffer,
                                preBufferSize,
                                 0 );
     if (preReceivedLength  < 0 ) {
        sockClass::error_info( " recv() failed. " );
    }  else if (preReceivedLength  == 0 ) {
        std::cout  << " Client has been disconnected./n " ;
         return 0 ;
    }
     return preReceivedLength;
}
int TCPClientSock::TCPReceive()  const
{
    preReceivedLength  = recv(    sockFD,
                                preBuffer,
                                preBufferSize,
                                 0 );
     if (preReceivedLength  < 0 ) {
        sockClass::error_info( " recv() failed. " );
    }  else if (preReceivedLength  == 0 ) {
        std::cout  << " Disconnected from server./n " ;
         return 0 ;
    }
     return preReceivedLength;
}
可以看到这2个方法也几乎是一模一样——除了名字和对异常信息的描述。因为我们这里并不知道需要recv()的确切长度,所以这里的TCPReceive()也跟recv()一样,有数据就返回。需要验证数据长度的,比如echo服务,我们另外写验证长度的代码。
最后需要说明的是,虽然SYN和FIN都会占用一个字节的数据,但是对于应用层的send()和recv()来说是不可见的。FIN会让recv()返回0,表示连接正常断开。
七。TCP连接的关闭
TCP连接一旦建立,服务器端和客户端就成为了对等关系,任何一方都可以发出关闭握手请求,甚至可以同时发出关闭握手请求。TCP的连接建立需要3次握手,而正常关闭则需要4次握手。
1、主动关闭的一方A调用close(),SendQ不再接收send()写入信息,在SendQ队列的最后,向被动关闭的一方发送TCP的IP数据报作为关闭握手的请求。这个数据报中包含着标志FIN,也包含着此刻的字节序号m。
2、B接收到第一次关闭握手请求后马上返回一个数据报作为回应。因为B接收到了FIN作为关闭连接的一个字节的数据,所以返回的字节序号是m+1。当A接收到B的这个回应,也即是第二次握手以后,表明确认在A到B的方向上不再有数据传送,A即转入所谓半关闭状态,等待B的关闭请求。而B收到FIN会导致recv()返回零,让应用层知道A到B的连接已经断开。
3、B方通知了应用层后也就进入等待关闭的状态。当B开始进入关闭流程,也会由B向A发送一个FIN,同时包含着B到A通讯方向上此刻的字节序号n。
4、A接收到B的这个FIN之后,也会将序号n+1反馈给B,自此,表明B到A的方向上不再有数据传送,TCP连接正式成功关闭。
以上只是对TCP连接关闭的简单描述,事实上,除了使用close()关闭,还可以使用shutdown(),这样在“半关闭”状态下还可以对TCP做其他的利用,具体内容就请大家自己查阅相关资料了。
最后,送上本人对于TCP连接的理解——“双向的单行道”——分别建立连接,也分别断开连接。

Linux下Socket编程之TCP原理相关推荐

  1. Linux下Socket编程之TCP应用

    现在,我们用前面所构建的socket类,重新设计<Linux下Socket编程之TCP Server端>中echo的服务器,然后设计客户端程序. echo服务器的工作原理很简单: 1.接收 ...

  2. Linux下Socket编程之UDP原理

    一.设计UDP Server类 人们通常用电话连线来说明TCP协议,而UDP协议,则常常用邮递来做比喻.与TCP有连接的信息传输方式不同,UDP协议被认为是对底层IP协议简单的扩展:协议并不保证每个数 ...

  3. Linux下Socket编程之TCP Server端

    一.建模 绝大部分关于socket编程的教程总是从socket的概念开始讲起的.要知道,socket的初衷是个庞大的体系,TCP/IP只是这个庞大体系下一个很小的子集,而我们真正能用上的更是这个子集中 ...

  4. 基于Linux的Socket编程之TCP全双工Server-Client聊天程序

    转载:http://blog.csdn.net/apollon_krj/article/details/53437764#0-tsina-1-58570-397232819ff9a47a7b7e80a ...

  5. Linux下socket编程之UDP简单实现

    本文实现一个简单的UDP小例子,来说明Linux下socket编程之UDP的简单实现.本文主要包括三个部分:服务器端的实现,客服端的实现和通信测试.实现的功能:客服端发送一条消息给服务器端,服务器端把 ...

  6. 基于Linux的SOCKET编程之TCP半双工Client-Server聊天程序

    转自:http://blog.csdn.net/apollon_krj/article/details/53398448#0-tsina-1-64987-397232819ff9a47a7b7e80a ...

  7. Linux C socket 编程之TCP

    本文主要是,简单实现tcp连接的两个程序.本文编写,假设读者有socket 编程思想.熟悉C编程. 服务端: #include <stdio.h> #include <stdlib. ...

  8. linux下简单的shellfor循环程序,对Linux下shell编程之for循环的实例讲解

    对Linux下shell编程之for循环的实例讲解 linux 下 for 循环中可以使用 break 和 continue 关键字来跳出循环, 和java 用法一致 一.常用for循环结构 #语法一 ...

  9. linux socket编程之TCP与UDP

    转:http://blog.csdn.net/gaoxin1076/article/details/7262482 TCP/IP协议叫做传输控制/网际协议,又叫网络通信协议 TCP/IP虽然叫传输控制 ...

最新文章

  1. 台式计算机刚换的显示屏怎么设置,台式机怎么样切换显示器
  2. python画动图-Python绘制动态水球图过程详解
  3. 艰难的这年,程序员的未来在哪里?
  4. Javascript面向对象编程(二):构造函数的继承
  5. linux grub设置cpu频率,Linux:使用性能调控器时,为什么CPU频率会发生波动?
  6. 最大化平均值 (二分搜索法)
  7. C#中利用Socket实现网络语音通信[初级版本]
  8. 【转】Azure 应用服务计划概述
  9. 计算机应用基础知识点.pdf,计算机应用基础知识点11.pdf
  10. [bzoj 3110] [ZJOI2013] K大数查询
  11. 事件---------2
  12. 谈谈我在自然语言处理入门的一些个人拙见
  13. PyMining-开源中文文本数据挖掘平台 Ver 0.1发布
  14. Leetcode. 14. Longest Common Prefix
  15. 学习日记:scipy库的版本差异
  16. 电源控制环稳定性基础理论与调试方法
  17. 一个月薪 12000 的北京程序员的真实生活
  18. “如果Java受到一两个大型供应商的控制,则可能会遭受挫折”
  19. python方差齐性检验_方差分析中的方差齐性检验_方差齐性检验结果分析
  20. html1 初入html

热门文章

  1. caj打印PDF提示打印超范围应该怎么办?
  2. arduino点阵声音频谱_Arduino实现32分频音频频谱显示器
  3. Image Signal Processing(ISP)-第二章-Demosaic去马赛克以及BMP软件实现
  4. 【Linux操作系统】——Linux命令
  5. Auto Layout和UILabel
  6. iPhone刷门禁卡的设置方法
  7. Linux 显示文件内行号显示
  8. 华为那个手机是鸿蒙,EMUI 11就是鸿蒙前奏 华为手机全面升级鸿蒙OS稳了
  9. 外卖返利系统电影吃喝玩乐团购返利系统外卖探探外卖券儿外卖cps系统saas源码
  10. Linux ssh免密登录