第2章 基本的TCP套接字

2.1 IPv4 TCP客户端
    4个步骤:
(1) socket()创建TCP套接字(window下要用初始化套接字环境)
(2) connect()建立到达服务起的连接
(3) send()和recv() 通信
(4) close关闭连接(Windows 下使用closesock()) 

2.1.1 应答(echo)协议的客户端
程序代码如下:
文件:practical.h,错误处理函数

#ifndef __PRACTICAL_H__
#define __PRACTICAL_H__

#define MAXLINE 4096 /* max line length */

extern void err_sys(const char *fmt, ...);
extern void err_quit(const char *fmt, ...);
#endif /* practical.h */

文件:practical.c

#include "practical.h"
#include <stdarg.h> /* ISO C varibale arguments: va_list, va_start(), va_end()*/
#include <stdio.h> /* vsnprintf(), snprintf(), fputs(), fflush() */
#include <errno.h> /* errno */
#include <string.h>    /* strcat(), strerror(), strlen()*/
#include <stdlib.h> /* exit() */
/*
 * Print a message and return to caller.
 * Caller specifies "errnoflag."
 */
static void 
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
 char buf[MAXLINE];
 vsnprintf(buf, MAXLINE, fmt, ap);
 if (errnoflag) {
  snprintf(buf + strlen(buf), MAXLINE - strlen(buf),
   ": %s", strerror(error));
 }
 strcat(buf, "\n");
 fflush(stdout);  /* in case stdout and strerr are the same*/
 fputs(buf, stderr); 
 fflush(NULL);  /* flushes all stdio output streams */
}

/*
 * Fatal error related to a system call.
 * Print a message and terminate.
 */
void err_sys(const char *fmt, ...)
{
 va_list ap;

 va_start(ap, fmt);
 err_doit(1, errno, fmt, ap);
 va_end(ap);
 exit(1);
}
/*
 * Fatal error unrelated to a system call.
 * Print a message and terminate.
 */
void err_quit(const char *fmt, ...)
{
 va_list ap;
 
 va_start(ap, fmt);
 err_doit(0, 0, fmt, ap);
 va_end(ap);
 exit(1);
}

 

文件:tcp_echo_client.c

#include <stdio.h>  /* fputs */
#include <stdlib.h>  /* exit, atoi, memset */
#include <unistd.h>  /* standard symbolic constants and types and miscellaneous functions: _POSIX_VERSION, _XOPEN_VERSION, R_OK, W_OK, SEEK_SET, SEED_CUR, F_LOCK,      STDIN_FILENO, function declarations and so on */
#include <sys/types.h> /* system data types: clock_t, dev_t,gid_t, mode_t, off_t, pid_t, size_t, ssize_t, time_t, uid_t and so on*/
#include <sys/socket.h> /* socket, connect, bind, listen, accept, send, sendto, sendmsg, recv, recvform, recvmsg */
#include <netinet/in.h> /* socket address:sockaddr, sockaddr_in sockaddr_in6 and so on or old system: difine htons, htonl, ntohs, ntohl*/
#include <arpa/inet.h> /*inet_ntop, inet_pton, htons, htonl, ntohs, ntohl */
#include "practical.h" 
 

int main(int argc, char *argv[])
{
 //Test for correct number of arguments
 if (argc < 3 || argc > 4) {
  err_quit("Usage: a.exe <server address> <echo word> [<server port>]");
 }
 
 char *servip = argv[1];  //first arg: server IP address(dotted quad)
 char *echo_string = argv[2]; //second arg: string to echo

 //Third arg(optional): server prot(numeric). 7 is well_known echo port
 in_port_t servport = (argc == 4) ? atoi(argv[3]) : 7;

 //Create a reliable, stream socket using TCP
 int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 if (sock < 0) {
  err_sys("socket failed");
 }
 
 //Construct the server address structure
 struct sockaddr_in servaddr;  //IPv4 server address
 memset(&servaddr, 0, sizeof(servaddr));
 servaddr.sin_family = AF_INET;  //IPv4 address family

 //Convert address
 int rtnval = inet_pton(AF_INET, servip, &servaddr.sin_addr.s_addr);
 if (rtnval == 0) {
  err_quit("inet_pton failed: invalid address string");
 } else if (rtnval < 0) {
  err_sys("inet_pton failed");
 }
 servaddr.sin_port = htons(servport); //server port
 
 //Establish the connection to the echo server
 if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
  err_sys("connect failed");
 }
 size_t echo_stringlen = strlen(echo_string);
 
 //Send the string to the server
 ssize_t numbytes = send(sock, echo_string, echo_stringlen, 0);
 if (numbytes < 0) {
  err_sys("send() failed");
 } else if (numbytes != echo_stringlen) {
  err_quit("send(), send unexpected of bytes");
 }

 //Receive the same string back from the server
 unsigned int totalbytesrecvd = 0; //count of total bytes received
 fputs("Received: ", stdout);
 while (totalbytesrecvd < echo_stringlen) {
  char buf[BUFSIZ]; //I/O buffer

  //Receive up to the buffer size (minus 1 to leave space for a
  //null terminator) bytes from the sender
  numbytes = recv(sock, buf, BUFSIZ - 1, 0);
  if (numbytes < 0) {
   err_sys("recv() failed");
  } else if (numbytes == 0){
   err_quit("connection closed prematurely");
  }
  totalbytesrecvd += numbytes;
  buf[numbytes] = '\0';
  fputs(buf, stdout);
 }
 fputs("\n", stdout);

 close(sock);
 exit(0);
}

    注意TCP是一种字节流协议,它的一种实现是不会保持send()边界。通过在连接的一端调用send()发送的字节可能不会通过在另一端单独调用一次recv而全部返回,需要反复接受。 
    编写套接字应用程序基本原则对于另一端的网络和程序将要做什么事情,永远不要做出假设
    在给用户提示信息是,若需要格式化则使用printf(),否则使用fputs()。应该避免使用printf输出固定的,预先格式化的字符串。你从来都不应该把从网络收到的文本作为第一个参数传递给printf(),这会引起严重的安全问题,要用fputs()。 

2.2 IPv4 TCP服务器
    服务器职责是建立通信端点,被动等待客户的连接。
    (1)socket()创建TCP套接字
    (2)利用bind()给套接字分配端口
    (3)listen()告诉系统允许对端口建立连接
    (4)反复执行以下操作:
        a.调用accept为每个客户连接获取新的套接字
        b.使用send()和recv()通过新的套接字与客户通信
        c.使用close()关闭客户连接。
程序代码如下:
文件:tcp_echo_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "practical.h"

void handle_tcp_client(int);

static const int MAXPENDING = 5; //maxinum outstanding connection requsts

int main(int argc, int argv[])
{
 if (argc != 2) {
  err_quit("Usage: a.exe <server prot>");
 }

 in_port_t servport = atoi(argv[1]); //first arg: local port

 //Create socket for incoming connections
 int servsock;
 if ((servsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))< 0) {
  err_sys("socket() failed");
 }

 //Construct local address structure
 struct sockaddr_in servaddr;
 memset(&servaddr, 0, sizeof(servaddr));
 servaddr.sin_family = AF_INET;
 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //Any incoming interface
 servaddr.sin_port = htons(servport);    //Local port

 //Bind to the local address
 if (bind(servsock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
  err_sys("bind() failed");
 }
 
 //Make the socket so it will listen to incoming connections
 if (listen(servsock, MAXPENDING) < 0) {
  err_sys("listen() failed");
 }
 
 for (;;) {
  struct sockaddr_in clntaddr;
  //Set length of client address structure(in-out parameter)
  socklen_t clntaddrlen = sizeof(clntaddr);

  //Wait for a client to connect
  int clntsock = accept(servsock, (struct sockaddr*)&clntaddr, &clntaddrlen);
  if (clntsock < 0) {
   err_sys("accept() failed");
  }

  //Clntsock is connected to a client
  char clntname[INET_ADDRSTRLEN];  //String to contain client address IPv4
  if (inet_ntop(AF_INET, &clntaddr.sin_addr.s_addr, clntname, sizeof(clntname)) != NULL) {
   printf("Handling client %s/%d\n", clntname, ntohs(clntaddr.sin_port));
  } else {
   fputs("Unable to get client address", stdout);
  }

  handle_tcp_client(clntsock);
 } //end while(true)

}

void handle_tcp_client(int clntsock)
{
 char buf[BUFSIZ];  //Buffer for echo string
 
 //Reveive message from client
 ssize_t num_recvd = recv(clntsock, buf, BUFSIZ, 0);
 if (num_recvd < 0) {
  err_sys("recv() failed");
 }
  
 //Send received string and receice again until end of stream
 while (num_recvd > 0) {
  //Echo message back to client
  ssize_t num_sent = send(clntsock, buf, num_recvd, 0);
  if (num_sent < 0) {
   err_sys("send failed");
  } else if (num_sent != num_recvd){
   err_sys("send(): sent unexpected number of bytes");
  }
  
  //See if there is more data to receice
  num_recvd = recv(clntsock, buf, BUFSIZ, 0);
  if (num_recvd < 0) {
   err_sys("recv() failed");
  }

  close(clntsock);  //Close client socket
 }

}

 

运行时:windows 下建议安装Cygwin,配置vim
    gcc practical.c tcp_echo_client.c -o client       生成client
    gcc practical.c tcp_echo_server -o server        生成server

先运行:server.out 端口号(如:5000)
client IPv4地址  显示字符串  端口号(5000)。

    与客户端的不同:服务器中套接字的使用必须涉及一个地方绑定到套接字,然后把该套接字用作获得连接到客户的其他套接字的方式。客户必须给connect提供服务器的地址,而服务器必须给bind()指定它自己的地址。他们要对这份信息(服务器的地址和端口)达成协议进行通信,它们实际上都不需要知道客户的地址。
    编写套接字网络应用程序的关键是:防御型编程,你的代码绝对不能对通过网络接受到的任何信息做出假设。
    可以使用telnet 连接到服务器,telnet IP地址

2.3 创建和销毁套接字
    套接字是通信端点的抽象,访问套接字需要套接字描述符,在unix下是用文件描述符实现的。许多处理文件描述符的函数(read, write等)可以处理套接字描述符。
    #include <sys/socket.h>
    int socket(int domain, int type, int protocol)
                            返回值:成功返回文件(套接字)描述符,失败返回-1
用于创建套接字的实例。
    domain:套接字的通信领域,
                AF_INET            IPv4
                AF_INET6          IPv6
                AF_UNIX           UNIX域(AF_LOCAL域)
                AF_UNSPEC     任何域
    type:套接字类型,进一步确定通信特征
                SOCK_DGRAM        长度固定的无连接的不可靠报文传递UDP默认协议
                SOCK_RAW            IP协议的数据报接口(POSIX.1中可选)
                SOCK_SEQPACKET  长度固定有序,可靠的面向连接报文传递
                SOCK_STREAM       有序可靠双向的面向连接字节流TCP默认协议
    protocol:端到端协议,通常为0,代表默认。SOCK_SEQPACKAGE与SOCK_STREAM类似,但从套接字得到的是基于报文服务的而不是字节流服务。这意味着它的套接字接收的数据量与对方发送的一致。流控制传输协议(Stream Control Transmission Protocol, SCTP)提供顺序数据报服务。SOCK_RAW套接字提供一个数据报接口用于直接访问下面的网络层(IP层)。使用这个接口时,应用程序负责构造自己的协议首部,传输协议(TCP, UDP)被绕过。当创建一个原始套接字时需要超级用户特权,用以防止恶意程序绕过内建安全机制来创建报文。
    调用socket类似于open,在两种情况下均可以获得用于输入输出的文件描述符。不需要描述符时,调用close,释放该描述符以便重新使用,套接字描述符本质上一个文件描述符,但不是所有参数为文件的函数都可以接受套接字描述符。

    关闭套接字
    #include <sys/socket.h>
    int close(int socket);
函数告诉底层协议栈发起关闭通信以及释放与套接字关联的资源
     套接字关闭是双向的,使用
    #include <sys/socket.h>
    int shutdown(int sockfd, int how); 
                                 返回值:成功0,失败-1
禁止套接字上的输入输出。
    how: SHUT_RD,关闭读端,无法从套接字读取数据。
            SHUT_WR ,关闭写端,无法从套接字发送数据。
            SHUT_RDWR, 关闭读写。

2.4 指定地址
    要确定通信目标进程,使用网络地址确定想要与之通信的计算机,服务(端口号)标识计算机上特定进程
    字节序列:大端法低位地址标识高位字节(类似于我们写的数字,地址由低到高,左边为高位)。小端法相反,低位地址表示低位字节。(inter平台,小端,Power PC,SUN大端)
    网络字节序列使用大端法,因此需要处理器字节序列与网络字节序列的转换。
    #include  <arpa/inet.h>                //某些老系统<netinet/in.h>
    uint32_t    htonl(uint32_t  hostint32);        //主机字节序列转换到网络字节序列long int(32位)
                                            返回值:以网络字节序列表示的32位整形
    uint16_t    htons(uint16_t  hostint16);        //主机字节序列转换到网络字节序列short int(16位),常用于转换Port号
                                            返回值:以网络字节序列表示的16位整形
    uint32_t    ntohl(uint32_t  hostint32);        //网络字节序列long int(32位)转换到 主机字节序列 
                                            返回值:以主机字节序列表示的32位整形
    uint16_t    ntohl(uint16_t  hostint16);       //网络字节序列long short(16位)转换到 主机字节序列                                           
                                            返回值:以主机字节序列表示的16位整形

2.4.1 通用地址
    以一个泛型地址用于传递给套接字函数。其它地址必须强制转换为它。
    #include <netinet/in.h>            //定义了所有地址
    struct   socketaddr  {
        sa_family_t    sa_family;            //Address family (e.g., AF_INET(6))
        char                sa_data[];            //variable-address length
    }
    套接字实现可以自由的添加额为的成员和定义sa_data[]的大小。
    在Linux下,
    struct   socketaddr  {
        sa_family_t    sa_family;
        char                sa_data[14];
    }    
    FreeBSD下,
    struct   socketaddr  {
        unsigned  int   sa_len;                //total  length
        sa_family_t    sa_family;
        char                sa_data[14];
    }    

2.4.2   IPv4地址
    sockaddr的结构依赖于IP版本
    struct   in_addr {
        in_addr_t  s_addr;                //IP address
    }
    
    struct  sockaddr_in{
        sa_family_t    sin_family;                //internet  procotol  (AF_INET)
        in_port_t        sin_port;                    //address port    (16 bits)
        struct    in_addr    sin_addr;            //IPv4 address (32 bits)
        char     sin_zero[8 ];                        //not used
    }
    in_port_t 是uint16_t, in_addr_t是uint32_t,都被定义在<stdint.h>中
    sockaddr_in只是sockaddr结构的更详细视图,是为IPv4套接字定制的。把sockaddr_in强制转换为sockaddr时,套接字函数会检测sa_familiy字段获取实际类型,然后强制转换为合适类型。

2.4.3 IPv6地址
    struct  in6_addr {
        uint8_t  s_addr[16];
    }
    struct  sockaddr_in6 {
        sa_family_t  sin6_family;            //AF_INET6
        in_port_t      sin6_port;
        uint32_t        sin6_flowinfo;        //traffic class and flow  information
        struct  in6_addr sin6_addr;        //IPv6 address (128 bits)
        uint32_t        sin6_scope_id;      //set of interface of scope
    }

2.4.4 通用地址存储器
    sockaddr不足以存放sockaddr_in6,在我们想为一个地址结构分配大小时,但是不知道实际类型(4或6),sockaddr不工作,因为它对与某些结构大小。使用sockaddr_storage结构,保证与任何支持的地址类型一样大
struct    sockaddr_storage  {
    sa_family_t                    //前导地址组字段,确定地址的实际类型
    ...
}
    在一些系统,地址结构包含一个额外的存储地址结构长度(字节为单位)的字段。由于长度字段并非所有系统都可用,应该避免使用,通常系统会提供一个值用于测试长度是否存在。

2.4.5二进制/字符串地址转换
    套接字函数只能理解数字(二进制形式),但是人们使用的是“可打印字符串(如:192.178.2.2,1::1)”。
    #include <arpa/inet.h>
    const  char *inet_ntop(int domain, const void *restrict addr, char *restrict str, socket_t size);
                        返回值:成功返回地址字符串指针,出错返回NULL(将数字(二进制网络地址)转换为23.24.54.4类型地址)
    int inet_pton(int domain, const char *restrict str, void *restrict addr);
                         返回值:成功返回1,格式无效返回0,出错返回-1(pton=printable to numeric)(将23.3.4.5类型地址转换为数字,网络地址(二进制))
    domain:地址族,只有AF_INET或AF_INET6
    对于inet_ntop(), size:保存文本缓存区(可打印的地址str)的大小。两个常数用于简化工作,INET_ADDRSTRLEN定义足够大的空间(可能最长的结构字符串)存放IPV4地址INET6_ADDRSTRLEN定义用于存放IPv6的空间
    对于inet_pton(),str是一个null结尾的字符串,addr要足够大,IPv4至少32为,IPv6至少128位。

2.5将套接字与地址绑定
   与客户端关联的套接字地址没有太大意义,可以让系统选择一个默认的地址。对于服务器需要给接收客户端请求的套接字绑定一个众所周知地址。客户端可以为服务器保留一个地址并在/etc/services或在某个名字服务(name service)中注册
    #include <sys/socket.h>
    int bind(int sockfd, struct sockaddr *localaddress, socket_t len);     
                        返回值:成功返回0,出错返回-1
    sockaddr设置为通配符INADDR_ANY(IPv4),IN6ADDR_ANY(IPV6),这是套接字可绑定到所有的系统网络接口,意味着可以接收系统所有网卡的数据报。调用connect和listen时没有绑定到一个套接字,系统会选择一个地址绑定到套接字
    可以使用IN6ADDR_ANY_INIT把in6_addr结构初始化为通配符地址,但是这个常量只能用于声明中的“初始化器”。注意:inaddr_any是主机字节序列,在用作bind()的参数之前必须先转换为网络字节序列。in6addr_any和in6addr_any_init已经是网络字节序列。
    当端口号为0提供给bind(),系统将为你选择未使用的本地端口

2.6.获取套接字的关联地址
    获取套接字关联的本地地址
    #include <sys/socket.h>
    int  getsockname(int sockfd, struct sockaddr *localaddress, socket_t *addresslength);
                    返回值:成功放回0,出错返回-1
获取套接字关联的外部地址,套接字已经和对方连接,用于获取对方的地址
    #include <sys/socket.h>
    int getpeername(int sockfd, struct sockaddr *remoteaddress, socket_t *addresslength);
                            返回值:成功放回0,出错返回-1
    sockfd: 想要获取其地址的套接字描述符。
    remoteaddress和localaddress指向实现把地址信息存放在其中的地址结构,总会被强制转换为sockaddr*。若事先不知道IP协议版本,可以传入一个sockaddr_storage*接受结果。
    addresslength:输入/输出型参数,是指向整形的指针(输入),整形指定sockaddr的大小,返回时(输出)被设置成返回地址的大小。若该地址与缓冲区长度不匹配,将其截断不报错。若没有绑定到套接字的地址,结果无意义。

2.7建立连接
    处理面向连接的网络服务(SOCK_STREAM或SOCK_SEQPACKAGE),需要客户端套接字与服务区端套接字进行连接。
    #include <sys/socket.h>
    int connect(int sockfd, const struct sockaddr *foreignaddress, socklen_t addresslen);
                        返回值:成功返回0,出错返回-1
connect中的地址foreignaddress是想要与之通信的地址,如果套接字没有绑定到一个地址,connect会给调用者绑定一个默认接口。
addresslength指定地址结构的长度,通常给出sizeof(struct sockaddr_in)或sizeof(struct sockaddr_in6)。

2.8处理进入的连接
 绑定后服务器套接字就具有一个地址(或至少一个套接字)。指示底层实现侦听来自套接字
的连接使用listen:
 #include <sys/socket.h>
 int listen(int socket, int queuelimit);
    返回值:成功0,失败-1
queuelimit:任意时间等待进入连接数量的上限。实际值由系统指定,上线由<sys/socket.h>
中SOMAXCONN指定。一旦队列满了就拒绝处理连接的请求
 listen()导致内部状态改变为给定的套接字,使得将会处理进入的TCP连接请求。然后对
它们进行排队。
     一旦把套接字配置为侦听,就可以开始接受其上客户的连接。首先,服务器现在似乎应该等待
它设置的套接字上的连接,通过套接字进行发送和接收,关闭它,然后重复这个过程,但是实际
上不是这样。已经被绑定到端口并且标记为“侦听”的套接字实际上从来不会用于发送和接收
。它被代之用作获取新套接字的方式,其中每个新套接字用于一条客户连接,服务器然后在
新套接字上执行发送和接收。


 调用accept获取用于连接的套接字
 #include <sys/socket.h>
 int accept(int sockfd, struct sockaddr *clientaddress, socket_t *addresslength);
    返回值:成功返回文件(套接字)描述符,失败返回-1
    返回的套接字连接到connect的客户端。这个新的套接字描述符和原始套接字(sockfd)具有相同的套接字
类型和地址族。传给accept的原始套接字没有关联到这个连接,而是继续保持原来状态并接受其它连接
请求。
    这个函数为套接字使队列的下一条连接出队,如果队列为空,就阻塞,直到下一个连接请求到达
成功时就会利用连接到另一端的客户的地址和端口填充由clientaddr指定的结构的大小(即可用的空间
),一旦返回,就会包含反回的实际地址的大小。成功,返回连接到客户的新套接字的描述符。一旦失败
返回-1,大多数系统仅当传递了一个错误的套接字描述符时,accept才会失败。在一些平台,如果套接字
在创建后并在被接受前经历了网络级错误,那么它可能返回一个错误。 

    若不关心客户端标识,可以将参数addr和len设为NULL,否则在accept之前,应将参数clientaddr设为足够大的 缓冲区

来存放地址,并将addresslength设为设为代表这个缓冲区大小的整数的指针。返回时,accept会在缓冲区
填写客户端的地址并且更新指针addresslength所指向的整数为该地址大小。
     如果没有连接请求,就阻塞到一个请求到来,如果sockfd是非阻塞模式,accept返回-1并将error设为
EAGAIN或EWOULDBLOCK(很多平台,EAGIN定义于EWOULDBLOCK相同)。

 

 2.9 数据传输
 在连接后,服务器和客户端的区别就消失了(至少socket api是这样)。连接的TCP套接字可以使用
 #inlcude <sys/socket.h>
 ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
   返回值:返回发送的字节数,出错返回-1
发送数据。它与write类似,但是可以指定标志来改变处理传输数据的方式。使用send时套接字必须已经连接。
参数buf和nbytes和write中相同。
 flags:
 MSG_DONTROUTE:勿将数据路由出本地网络。
 MSG_DONTWAIT: 允许非阻塞操作(等价于O_NONBLOCK)
 MSG_EOR:      如果协议支持,此为记录结束。
 MSG_OOB:  如果协议支持,发送外带数据。
     send成功返回,并不必然表示连接另一端的进程接收数据只表示数据已经无错误的发送到网络
对于支持为报文设限的协议,如果单个报文超过协议所支持的最大尺寸,send失败并将error设为EMSGSIXZE,
对于字节流协议,send会默认会阻塞直到整个数据被传输


 #include <sys/socket.h>
 ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
   返回值:以字节计数的消息长度,若无可用消息或对方已经按序结束则返回0,出错返回-1
 函数recv与read类似,但允许指定选项控制如何接受。
 flag
 MSG_OOB:    如果协议支持,接受外带数据
 MSG_PEEK: 返回报文内容而不真正取走报文
 MSG_TRUNC: 即使报文被截断,要求返回的是报文的实际长度
 MSG_WAITALL:等待直到所有的数据可用(仅SOCK_STREAM)
 
 当指定MSG_PEEK标志时,可以查看写一个要读的数据而不真正取走再次调用read或recv函数会返回
刚才查看的数据。

对于SOCK_STREAM套接字,接受的数据可以比请求的少MSG_WAITALL阻止这种行为除非需要的数据
全部接受recv才返回。对于sock_DGRAM和SOCK_SEQPACKET套接字,它不改变什么行为,因为基于报文的套接字
类型一次读取就返回整个报文。
 如果发送者已经调用shutdown结束传输,或者网络协议支持默认的顺序关闭并且发送端已经关闭,那么
所有的数据接受完毕后recv返回0
 buf指向缓冲区,其中存放接收到的数据,len为缓冲区的长度,它是一次可以接受的最大字节数。recv默认
行为阻塞到至少传输一些字节位置(在多数系统上,将导致recv的通用者解除阻塞的最小数据量是1字节)。
 注意:TCP是一种字节流协议,不会保留send()边界。在接收者上调用recv()一次所读取的字节数不一定
由调用send()一次所写入的字节数确定。

 

 2.10使用IPv6
 IPv6程序涉及了IPv6的地址结构和常量,其它没有什么不同。

 socket(AF_INTE6, SOCK_STREAM, IPPROTO_TCP);
 struct sockaddr_in6 servaddr;
 memset(&servaddr, 0, sizeof(servaddr));
 servaddr.sin6_family = AF_INET6;
 servaddr.sin6_addr   = in6addr_any;  //不需要转换为网络字节序列,已经是了。
 servaddr.sin6_port   = htons(servport);
 IPv6的地址长度:INET6_ADDRSTRLE;

posted on 2014-04-16 12:47  hancmhi  阅读(517)  评论(0编辑  收藏  举报