zoukankan      html  css  js  c++  java
  • 《TCP/IP网络编程》读书笔记

    1.Windows 下的 socket 程序和 Linux 思路相同,但细节有所差别
    (1) Windows 下的 socket 程序依赖 Winsock.dll 或 ws2_32.dll,必须提前加载。DLL 有两种加载方式,请查看:动态链接库DLL的加载
    (2) Linux 使用“文件描述符”的概念,而 Windows 使用“文件句柄”的概念;Linux 不区分 socket 文件和普通文件,而 Windows 区分;
    (3) Linux 下 socket() 函数的返回值为 int 类型,而 Windows 下为 SOCKET 类型,也就是句柄。
    (4) Linux 下使用 read() / write() 函数读写,而 Windows 下使用 recv() / send() 函数发送和接收。
    (5) 关闭 socket 时,Linux 使用 close() 函数,而 Windows 使用 closesocket() 函数。

    2.分配给标准输入标准输出及标准错误输出的文件描述符
    文件描述符       对象
     0                  标准输出
     1                  标准输出
     2                  标准错误
     
    3.WinSock(Windows Socket)编程依赖于系统提供的动态链接库(DLL),有两个版本: 
    较早的DLL是 wsock32.dll,大小为 28KB,对应的头文件为 winsock1.h;
    最新的DLL是 ws2_32.dll,大小为 69KB,对应的头文件为 winsock2.h。
    使用#pragma命令,在编译时加载:
     #pragma comment (lib, "ws2_32.lib")
    WinSock 编程的第一步就是加载 ws2_32.dll,然后调用 WSAStartup() 函数进行初始化,并指明要使用的版本号
    WSADATA wsaData;
    WSAStartup( MAKEWORD(2, 2), &wsaData);

    4. socket编程

    (1) 使用socket()函数创建套接字
    (2) 使用bind()函数将套接字与特定的IP地址和端口绑定起来,只有这样,流经该IP地址和端口的数据才能交给套接字处理
    (3) 使用connect()函数来建立连接
    (4) sockaddr结构体
    可以认为,sockaddr 是一种通用的结构体,可以用来保存多种类型的IP地址和端口号,而 sockaddr_in 是专门用来保存 IPv4 地址的结构体
    另外还有 sockaddr_in6,用来保存 IPv6 地址
    (5) 使用listen()函数让套接字进入被动监听状态
    (6) 使用accept()函数可以随时响应客户端的请求
    (7) accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号
    (8) listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到accept()
    (9) accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。
    (10) TCP服务器端函数调用顺序:socket()->bind()->listen()->accept()->read()/write()->close()
    (11) TCP客户端函数调用顺序:socket()->connect()->read()/write()->close()
     
     
    例子:
    (1)基于linux环境的tcp回声服务器的实现
     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <unistd.h>
     5 #include <arpa/inet.h>
     6 #include <sys/socket.h>
     7 
     8 #define BUF_SIZE 1024
     9 void error_handling(char *message);
    10 
    11 int main(int argc, char *argv[])
    12 {
    13     int serv_sock, clnt_sock;
    14     char message[BUF_SIZE];
    15     int str_len, i;
    16     
    17     struct sockaddr_in serv_adr;
    18     struct sockaddr_in clnt_adr;
    19     socklen_t clnt_adr_sz;
    20     
    21     if(argc!=2) {
    22         printf("Usage : %s <port>
    ", argv[0]);
    23         exit(1);
    24     }
    25     
    26     serv_sock=socket(PF_INET, SOCK_STREAM, 0);   
    27     if(serv_sock==-1)
    28         error_handling("socket() error");
    29     
    30     memset(&serv_adr, 0, sizeof(serv_adr));
    31     serv_adr.sin_family=AF_INET;
    32     serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    33     serv_adr.sin_port=htons(atoi(argv[1]));
    34 
    35     if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
    36         error_handling("bind() error");
    37     
    38     if(listen(serv_sock, 5)==-1)
    39         error_handling("listen() error");
    40     
    41     clnt_adr_sz=sizeof(clnt_adr);
    42 
    43     for(i=0; i<5; i++)
    44     {
    45         clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
    46         if(clnt_sock==-1)
    47             error_handling("accept() error");
    48         else
    49             printf("Connected client %d 
    ", i+1);
    50     
    51         while((str_len=read(clnt_sock, message, BUF_SIZE))!=0)
    52             write(clnt_sock, message, str_len);
    53 
    54         close(clnt_sock);
    55     }
    56 
    57     close(serv_sock);
    58     return 0;
    59 }
    60 
    61 void error_handling(char *message)
    62 {
    63     fputs(message, stderr);
    64     fputc('
    ', stderr);
    65     exit(1);
    66 }
    View Code

    (2)基于linux环境的tcp回声客户端的实现

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <unistd.h>
     5 #include <arpa/inet.h>
     6 #include <sys/socket.h>
     7 
     8 #define BUF_SIZE 1024
     9 void error_handling(char *message);
    10 
    11 int main(int argc, char *argv[])
    12 {
    13     int sock;
    14     char message[BUF_SIZE];
    15     int str_len;
    16     struct sockaddr_in serv_adr;
    17 
    18     if(argc!=3) {
    19         printf("Usage : %s <IP> <port>
    ", argv[0]);
    20         exit(1);
    21     }
    22     
    23     sock=socket(PF_INET, SOCK_STREAM, 0);   
    24     if(sock==-1)
    25         error_handling("socket() error");
    26     
    27     memset(&serv_adr, 0, sizeof(serv_adr));
    28     serv_adr.sin_family=AF_INET;
    29     serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
    30     serv_adr.sin_port=htons(atoi(argv[2]));
    31     
    32     if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
    33         error_handling("connect() error!");
    34     else
    35         puts("Connected...........");
    36     
    37     while(1) 
    38     {
    39         fputs("Input message(Q to quit): ", stdout);
    40         fgets(message, BUF_SIZE, stdin);
    41         
    42         if(!strcmp(message,"q
    ") || !strcmp(message,"Q
    "))
    43             break;
    44 
    45         write(sock, message, strlen(message));
    46         str_len=read(sock, message, BUF_SIZE-1);
    47         message[str_len]=0;
    48         printf("Message from server: %s", message);
    49     }
    50     
    51     close(sock);
    52     return 0;
    53 }
    54 
    55 void error_handling(char *message)
    56 {
    57     fputs(message, stderr);
    58     fputc('
    ', stderr);
    59     exit(1);
    60 }
    View Code

    5.Ack号 = Seq号 + 传递的字节数 + 1
     
    6.
     (1) 调用 close()/closesocket() 函数意味着完全断开连接,即不能发送数据也不能接收数据
     (2) 使用 shutdown() 函数可以只断开一条数据传输通道,而保留另一条
    int shutdown(int sock, int howto);  //Linux
    int shutdown(SOCKET s, int howto);  //Windows

    sock 为需要断开的套接字,howto 为断开方式

    howto 在 Linux 下有以下取值:
    • SHUT_RD:断开输入流。套接字无法接收数据(即使输入缓冲区收到数据也被抹去),无法调用输入相关函数。
    • SHUT_WR:断开输出流。套接字无法发送数据,但如果输出缓冲区中还有未传输的数据,则将传递到目标主机。
    • SHUT_RDWR:同时断开 I/O 流。相当于分两次调用 shutdown(),其中一次以 SHUT_RD 为参数,另一次以 SHUT_WR 为参数。
    howto 在 Windows 下有以下取值:
    • SD_RECEIVE:关闭接收操作,也就是断开输入流。
    • SD_SEND:关闭发送操作,也就是断开输出流。
    • SD_BOTH:同时关闭接收和发送操作。
    shutdown() 用来关闭连接,而不是套接字,不管调用多少次 shutdown(),套接字依然存在,直到调用 close() / closesocket() 将调用 close()/closesocket() 关闭套接字时,或调用 shutdown() 关闭输出流时,都会向对方发送 FIN 包。FIN 包表示数据传输完毕,计算机收到 FIN 包就知道不会再有数据传送过来了
     (3) 默认情况下,close()/closesocket() 会立即向网络中发送FIN包,不管输出缓冲区中是否还有数据,而shutdown() 会等输出缓冲区中的数据传输完毕再发送FIN包, 也就意味着,调用 close()/closesocket() 将丢失输出缓冲区中的数据,而调用 shutdown() 不会。
    例子:
    (1)file_server.c
     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <unistd.h>
     5 #include <arpa/inet.h>
     6 #include <sys/socket.h>
     7 
     8 #define BUF_SIZE 30
     9 void error_handling(char *message);
    10 
    11 int main(int argc, char *argv[])
    12 {
    13     int serv_sd, clnt_sd;
    14     FILE * fp;
    15     char buf[BUF_SIZE];
    16     int read_cnt;
    17     
    18     struct sockaddr_in serv_adr, clnt_adr;
    19     socklen_t clnt_adr_sz;
    20     
    21     if(argc!=2) {
    22         printf("Usage: %s <port>
    ", argv[0]);
    23         exit(1);
    24     }
    25     
    26     fp=fopen("file_server.c", "rb"); 
    27     serv_sd=socket(PF_INET, SOCK_STREAM, 0);   
    28     
    29     memset(&serv_adr, 0, sizeof(serv_adr));
    30     serv_adr.sin_family=AF_INET;
    31     serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    32     serv_adr.sin_port=htons(atoi(argv[1]));
    33     
    34     bind(serv_sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
    35     listen(serv_sd, 5);
    36     
    37     clnt_adr_sz=sizeof(clnt_adr);    
    38     clnt_sd=accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
    39     
    40     while(1)
    41     {
    42         read_cnt=fread((void*)buf, 1, BUF_SIZE, fp);
    43         if(read_cnt<BUF_SIZE)
    44         {
    45             write(clnt_sd, buf, read_cnt);
    46             break;
    47         }
    48         write(clnt_sd, buf, BUF_SIZE);
    49     }
    50     
    51     shutdown(clnt_sd, SHUT_WR);    
    52     read(clnt_sd, buf, BUF_SIZE);
    53     printf("Message from client: %s 
    ", buf);
    54     
    55     fclose(fp);
    56     close(clnt_sd); close(serv_sd);
    57     return 0;
    58 }
    59 
    60 void error_handling(char *message)
    61 {
    62     fputs(message, stderr);
    63     fputc('
    ', stderr);
    64     exit(1);
    65 }
    View Code

    (2)file_client.c

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <unistd.h>
     5 #include <arpa/inet.h>
     6 #include <sys/socket.h>
     7 
     8 #define BUF_SIZE 30
     9 void error_handling(char *message);
    10 
    11 int main(int argc, char *argv[])
    12 {
    13     int sd;
    14     FILE *fp;
    15     
    16     char buf[BUF_SIZE];
    17     int read_cnt;
    18     struct sockaddr_in serv_adr;
    19     if(argc!=3) {
    20         printf("Usage: %s <IP> <port>
    ", argv[0]);
    21         exit(1);
    22     }
    23     
    24     fp=fopen("receive.dat", "wb");
    25     sd=socket(PF_INET, SOCK_STREAM, 0);   
    26 
    27     memset(&serv_adr, 0, sizeof(serv_adr));
    28     serv_adr.sin_family=AF_INET;
    29     serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
    30     serv_adr.sin_port=htons(atoi(argv[2]));
    31 
    32     connect(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
    33     
    34     while((read_cnt=read(sd, buf, BUF_SIZE ))!=0)
    35         fwrite((void*)buf, 1, read_cnt, fp);
    36     
    37     puts("Received file data");
    38     write(sd, "Thank you", 10);
    39     fclose(fp);
    40     close(sd);
    41     return 0;
    42 }
    43 
    44 void error_handling(char *message)
    45 {
    46     fputs(message, stderr);
    47     fputc('
    ', stderr);
    48     exit(1);
    49 }
    View Code
     
    7. recv() 返回 0 的唯一时机就是收到FIN包时
     
    8. 网络字节序
    不同 CPU 保存和解析数据的方式不同(主流的 Intel 系列 CPU 为小端序),小端序系统和大端序系统通信时会发生数据解析错误。因此在发送数据前,要将数据转换为统一的格式——网络字节序(Network Byte Order)。网络字节序统一为大端序。
     
    9.
    ping 域名可以查看域名对应的IP地址
    nslookup命令可以查看计算机中注册的默认DNS服务器地址
     
    10.
     (1) 下列函数可以通过传递字符串格式的域名获取IP地址
    #include <netdb.h>
    struct hostent *gethostbyname(const char *hostname);

     成功时返回hostnet结构体指针,失败时返回NULL指针

    例子:gethostbyname.c

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <arpa/inet.h>
     5 #include <netdb.h>
     6 void error_handling(char *message);
     7 
     8 int main(int argc, char *argv[])
     9 {
    10     int i;
    11     struct hostent *host;
    12     if(argc!=2) {
    13         printf("Usage : %s <addr>
    ", argv[0]);
    14         exit(1);
    15     }
    16     
    17     host=gethostbyname(argv[1]);
    18     if(!host)
    19         error_handling("gethost... error");
    20 
    21     printf("Official name: %s 
    ", host->h_name);
    22     
    23     for(i=0; host->h_aliases[i]; i++)
    24         printf("Aliases %d: %s 
    ", i+1, host->h_aliases[i]);
    25     
    26     printf("Address type: %s 
    ", 
    27         (host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6");
    28 
    29     for(i=0; host->h_addr_list[i]; i++)
    30         printf("IP addr %d: %s 
    ", i+1,
    31                     inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
    32     return 0;
    33 }
    34 
    35 void error_handling(char *message)
    36 {
    37     fputs(message, stderr);
    38     fputc('
    ', stderr);
    39     exit(1);
    40 }
    View Code
    struct hostnet
    {
      char *h_name;
      char **h_aliases;
      int h_addrtype;
      int h_lenght;
      char **h_addr_list;
    }

     (2) gethostbyaddr()函数利用IP地址获取域相关信息

    #include <netdb.h>
    struct hostnet *gethostbyaddr(const char *addr, socklen_t len, int fanily);

     成功时返回hostnet结构体变量地址值,失败时返回NULL指针

     addr:含有IP地址信息的in_addr结构体指针
     len:  向第一个参数传递的地址信息的字节数,IPv4时为4,IPv6时为6
     family: 传递地址族信息,IPv4时为AF_INET, IPv6时为AF_INET6

    例子:gethostbyaddr.c

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <unistd.h>
     5 #include <arpa/inet.h>
     6 #include <netdb.h>
     7 void error_handling(char *message);
     8 
     9 int main(int argc, char *argv[])
    10 {
    11     int i;
    12     struct hostent *host;
    13     struct sockaddr_in addr;
    14     if(argc!=2) {
    15         printf("Usage : %s <IP>
    ", argv[0]);
    16         exit(1);
    17     }
    18 
    19     memset(&addr, 0, sizeof(addr));
    20     addr.sin_addr.s_addr=inet_addr(argv[1]);
    21     host=gethostbyaddr((char*)&addr.sin_addr, 4, AF_INET);
    22     if(!host)
    23         error_handling("gethost... error");
    24 
    25     printf("Official name: %s 
    ", host->h_name);
    26 
    27     for(i=0; host->h_aliases[i]; i++)
    28         printf("Aliases %d: %s 
    ", i+1, host->h_aliases[i]);
    29     
    30     printf("Address type: %s 
    ", 
    31         (host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6");
    32 
    33     for(i=0; host->h_addr_list[i]; i++)
    34         printf("IP addr %d: %s 
    ", i+1,
    35                     inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));    
    36     return 0;
    37 }
    38 
    39 void error_handling(char *message)
    40 {
    41     fputs(message, stderr);
    42     fputc('
    ', stderr);
    43     exit(1);
    44 }
    View Code
     
     (3)字节序转换函数
     unsigned short htons(unsigned short);
     unsigned short ntohs(unsigned short);
     unsigned long htonl(unsigned long);
     unsigned long ntohl(unsigned long);

     (4) 将字符串信息转换为网络字节序的整数型

     #include <arpa/inet.h>
     in_addr_t inet_addr(const char *string);

      成功时返回32位大端序整数型值,失败时返回INADDR_NONE

     (5) inet_aton()也将字符串形式IP地址转换位32位网络字节序整数并返回,只不过该函数利用in_addr结构体,且其使用频率跟高
     
    #include <arpa/inet.h>
    int inet_aton(const char *string, struct in_addr *addr);

      成功时返回1(true),失败时返回0(false)

     (6) inet_ntoa()函数可以把网络字节序整数型IP地址转换成字符串形式
     #include <arpa/inet.h>
     char *inet_ntoa(struct in_addr adr);

      成功时返回转换的字符串地址值,失败时返回-1;

            调用完该函数后应立即将字符串信息复制到其他内存空间,因为,若再次调用inet_ntoa函数,则有可能覆盖之前保存的字符串信息
     
     (7) 网络地址信息初始化方法:
     
    struct sockaddr_in addr;
    char *serv_ip = "211.217.168.13";
    char *serv_port "9190";
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(serv_ip);
    addr.sin_port = htons(atoi(serv_port));

     利用常数INADDR_ANY分配服务器的IP地址,可以自动获取运行服务器端的计算机IP地址

     addr.sin_addr.s_addr = htonl(INADDR_ANY);
    11.套接字的可选项
    getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
    setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen)
    • SO_SNDBUF:输入缓冲大小相关可选项
    •  SO_RCVBUF:输出缓冲大小相关可选项
    •  SO_REUSEADDR:该可选项设置为TRUE可将Time_wait状态下的套接字端口号重新分配给新的套接字
     TCP_NODELAY设置为1可禁用Nagle算法
     
    12.多进程
     (1) 通过调用fork函数创建进程
     #include <unistd.h>
     pid_t fork(void);

     父进程:fork函数返回子进程的ID

     子进程:fork函数返回0
    (2) 应当向创建子进程的父进程传递子进程的exit参数值或return语句的返回值,防止僵尸进程的产生
     销毁僵尸进程1:利用wait函数
    #include <unistd.h>
    pid_t wait(int *statloc);

     ->成功时返回终止的子进程ID,失败时返回-1

     子进程终止时传递的返回值将保存到statloc所指内存空间,需要用下列宏进行分离
    •  WIFEXITED 子进程正常终止时返回“真”true
    •  WEXITSTATUS 返回子进程的返回值
     销毁僵尸进程2:利用waitpid函数
     #include <sys/wait.h>
     pid_t waitpid(pid_t pid, int *statloc, int options);

     ->成功时返回终止的子进程ID,失败时返回-1

     pid 等待终止的目标子进程ID,若传递-1,则可以等待任意子进程终止

     (3) statloc与wait函数的statloc参数具有相同含义
     (4) options 传递sys/wait.h中声明的常量WNOHANG,即使没有终止的子进程也不会进入组赛状态,而是返回0并退出函数
     (5)信号与signal函数
     #include <signal.h>
     void (*signal(int signal, void (*func)(void)))(int);

     ->为了在产生信号时调用,返回之前注册的函数指针

     (6) 利用sigaction函数进行信号处理
     #include <signal.h>
     int sugacyion(int signo, const struct sigaction *act, struct sigaction *oldact);

     ->成功时返回0,失败时返回-1

     通过fork函数复制套接字文件描述符后,同一端口将对应多个套接字,只有这些套接字描述符都终止,才能销毁套接字
     
    13.进程间通信
     创建管道的函数:
     #include <unistd.h>
     int pipe(int filedes[2]);

     ->成功时返回0,失败时返回-1

     filedes[0]:通过管道接收数据时使用的文件描述符,即管道出口
     filedse[1]:通过管道传输数据时使用的文件描述符,即管道入口
     
    14.I/O复用
     (1) 针对fd_set变量的操着的宏:
     FD_ZERO(fd_set *fdset)
     FD_SET(int fd, fd_set *fdset)
     FD_CLR(int fd, fd_set *fdset)
     FD_ISSET(fint fd, d_set *fdset)

    (2) select函数:

     #include <sys/select.h>
     #include <sys/time.h>
     int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

    15.多种I/O函数

     (1) 收到MSG_OOB紧急消息时,操着系统将产生SIGURG消息,并调用注册的信号处理函数
     (2) 处理SIGURG信号时必须指定处理信号的进程,而geipid函数返回调用此函数的进程ID
     (3) fcntl函数用于控制文件描述符
     
    fcntl(recv_sock, F_SETOWN, getpid());

     上述调用的含义是“将文件描述符recv_sock指向的套接字拥有者(F_SETOWN)改为把getpid函数返回值用作ID的进程

     (4) 紧急指针指向紧急消息的下一个位置(偏移量+1),紧急消息的意义在于督促消息处理,而非紧急传输形式受限的消息
     (5) 调用recv函数的同时传递MSG_PEEK可选项,是为了保证即使不存在待读取的数据也不会进入阻塞状态,设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲的数据也不会删除,该选项通常与MSG_DONTWAIT合作,用于调用以非阻塞方式验证待读取数据存在与否的函数
  • 相关阅读:
    新公司面试记
    棋手与棋子
    有线通or ADSL?
    好久没去上海动物园了
    15=60
    技术和销售
    安能辨龟是雄雌巴西龟的雌雄辨别方法
    推荐两首好歌
    母亲节祝福天下所有的母亲
    农夫山泉的源头千岛湖游记
  • 原文地址:https://www.cnblogs.com/chenweilin/p/11816095.html
Copyright © 2011-2022 走看看