第6课.网络编程

Posted huangdengtao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第6课.网络编程相关的知识,希望对你有一定的参考价值。

1.TCP和UDP的区别

(1)TCP是面向连接的协议,UDP是面向无连接的协议。
(2)TCP对系统资源要求较多,UDP对系统资源要求较少。
(3)TCP是数据流模式,UDP是数据报模式。
(4)TCP保证数据顺序及数据的正确性,UDP可能会丢包。

2.简述TCP/UDP服务器端创建流程与客户端创建流程

TCP服务器端创建流程:

创建通信用文件描述符(socket)-->设置端口号和IP地址(为绑定做准备)-->绑定(bind)-->监听(listen)-->接受请求,建立连接(accept)-->发送与接收消息(send/recv)-->关闭文件(close)

TCP客户端创建流程:

创建通信用文件描述符(socket)-->设置端口号和IP地址-->发起连接请求(connect)-->接受与发送消息(send/recv)-->关闭文件(close)

UDP服务器端创建流程:

创建通信用文件描述符(socket)-->设置端口号和IP地址(为绑定做准备)-->绑定(bind)-->接受和发送消息(sendto && recvfrom)-->关闭文件(close)

UDP客户端创建流程:

创建通信用文件描述符(socket)-->设置端口号和IP地址-->接受与发送消息(sendto && recvfrom)-->关闭文件

技术图片

技术图片

3.函数解析

socket函数

获得一个文件描述符(文件句柄)。为了执行网络输入输出,一个进程必须做的一件事就是调用socket函数获得一个文件描述符(文件句柄)
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
成功:return 费负描述符        失败:-1
    domain: 指明协议簇,目前支持5中协议簇,最常用的有AF_INET (IPv4协议)和AF_INET6 (IPv6协议)
    type  : 指明套接口类型,有3种类型可选:
                SOCK_STREAM:字节流套接口
                SOCK_DGRAM :数据报套接口
                SOCK_RAW   :原始套接口;如果套接口不是原始套接口,那么第三个参数就为0

bind函数

把文件句柄和IP,端口捆绑在一起。为套接口分配一个本地IP和协议端口,对于网络协议,协议地址是32位IPv4或128位IPv6地址与16位的TCP/UDP端口的组合。如指定一个通用的通配端口为0,调用bind时内核将选择一个临时端口。如果指定一个通用通配IP地址,则要等建立连接后内核才能选择一个本地IP地址。
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
成功:0                        失败:-1
    sockfd :文件句柄
    addr   :指向特定协议的地址结构的指针
        这里不使用sockaddr这个结构体,而是使用下面这个结构体
        struct sockaddr_in { 
            short int sin_family;             /* 地址族,AF_xxx 在socket编程中只能是AF_INET */ 
            unsigned short int sin_port;      /* 端口号 (使用网络字节顺序) */ 
            struct in_addr sin_addr;          /* 存储IP地址 4字节 */ 
            unsigned char sin_zero[8];        /* 总共8个字节,实际上没有什么用,只是为了和struct sockaddr保持一样的长度,设为0就好 */ 
        }; 
    addrlen:第二个参数结构体的长度

listen函数

监听。listen函数仅被TCP服务器调用,它的作用是将用socket创建的主动套接口转换成被动套接口,并等待来自客户端的连接请求
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);
成功:0                        失败:-1
    sockfd :文件句柄
    backlog:最多可以一次监听多少路。这个参数规定了内核为此套接口排队的最大连接个数。由于listen函数第二个参数的原因。内核要维护两个队列。以完成连接队列和未完成连接队列。未完成队列中存放的是TCP连接的三路握手未完成的连接。accept函数是从以连接队列中取连接返回给进程,当已连接队列为空时,进程将进入睡眠状态。

accept函数

等待连接。accept函数由TCP服务器调用,从已完成连接队列头返回一个已完成连接,如果完成连接队列为空则进程进入睡眠状态
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
成功:非负描述符                失败:-1
    sockfd :文件句柄
    addr   :接收到的数据存储在这里。
    addrlen:同bind函数

connect函数

建立联系。当用connect建立了套接口后可以用connect为这个套接口指明远程端地址。如果字节流套接口(TCP),connect就使用三次握手建立一个链接。如果是数据报套接口,connect仅指明远程端地址,而不向它发送任何数据区握手。
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
成功:0                        失败:-1
sockfd :客户端文件句柄
addr   :见bind函数
addrlen:同上

send函数

用来发送数据
#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
成功:返回写出的字数            失败:-1
sockfd:文件句柄。对于服务器是accept函数返回的已连接的文件句柄。对于客户端是调研员socket函数返回的文件句柄
buf   :指向一个用于发送信息的数据缓冲区
len   :指明传送数据的大小
flags :传输标志

sendto函数

用于无连接(没有connect函数)的数据报,socket方式下进行传输。由于本地socket并没有与远端服务器建立联系,所以在发送数据时应指明目的地址
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
成功:返回写出的字数            失败:-1
dest_addr:用法和connect中一样
addrlen  :同上

recv函数

用来接收数据
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
成功:返回读入的字节数            失败:-1
参数同上

recvfrom函数

简介同sendto
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                    struct sockaddr *src_addr, socklen_t *addrlen);
参数同sendto

close函数

结束传输。当所有数据操作结束后,你可以调用close函数来释放socket,从而停止改socket上的任何操作
#include <unistd.h>

int close(int fd);
你也可以调用shutdown()函数来关闭socket。该函数允许你至停止在某个方向上的数据传输,而另一个方向上的数据传输继续进行。如你可以关闭socket的写操作而允许改socket继续接受数据直到读入所以数据。
int shutdown(int sockfb, int how);
成功:0                        失败:-1
sockfb:需要关闭的socket的文件句柄。
how   :允许shutdown操作选择
        0:不允许继续接收操作
        1:不允许继续发送操作
        3:不允许继续发送和接收操作

4.代码解析

TCP

server.c

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>

/*  socket
 *  bind
 *  listen
 *  accept
 *  send/recv
 */

#define SERVER_PORT 8888
#define BACKLOG     10

int main(int argc, char **argv)
{
    int iSocketServer;
    int iSocketClient;
    struct sockaddr_in tSocketServerAddr;
    struct sockaddr_in tSocketClientAddr;
    int iRet;
    int iAddrLen;

    int iRecvLen;
    unsigned char ucRecvBuf[1000];

    int iClientNum = -1;

    signal(SIGCHLD,SIG_IGN);    //防止子进程僵死

    iSocketServer = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == iSocketServer)
    {
        printf("socket error!
");
        return -1;
    }

    tSocketServerAddr.sin_family      = AF_INET;
    tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
    tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
    memset(tSocketServerAddr.sin_zero, 0, 8);

    iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
    if (-1 == iRet)
    {
        printf("bind error!
");
        return -1;
    }

    iRet = listen(iSocketServer, BACKLOG);
    if (-1 == iRet)
    {
        printf("listen error!
");
        return -1;
    }

    while (1)
    {
        iAddrLen = sizeof(struct sockaddr);
        iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
        if (-1 != iSocketClient)
        {
            iClientNum++;
            printf("Get connect from client %d : %s
",  iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
            if (!fork())
            {
                /* 子进程的源码 */
                while (1)
                {
                    /* 接收客户端发来的数据并显示出来 */
                    iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
                    if (iRecvLen <= 0)
                    {
                        close(iSocketClient);
                        return -1;
                    }
                    else
                    {
                        ucRecvBuf[iRecvLen] = '';
                        printf("Get Msg From Client %d: %s
", iClientNum, ucRecvBuf);
                    }
                }               
            }
        }
    }
    
    close(iSocketServer);
    return 0;

}

client.c

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>


/*  socket
 *  connect
 *  send/recv
 */

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
    int iSocketClient;
    struct sockaddr_in tSocketServerAddr;
    
    int iRet;
    unsigned char ucSendBuf[1000];
    int iSendLen;

    if (argc != 2)
    {
        printf("Usage:
");
        printf("%s <server_ip>
", argv[0]);
        return -1;
    }

    iSocketClient = socket(AF_INET, SOCK_STREAM, 0);

    tSocketServerAddr.sin_family      = AF_INET;
    tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
    if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
    {
        printf("invalid server_ip
");
        return -1;
    }
    memset(tSocketServerAddr.sin_zero, 0, 8);


    iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));    
    if (-1 == iRet)
    {
        printf("connect error!
");
        return -1;
    }

    while (1)
    {
        if (fgets(ucSendBuf, 999, stdin))
        {
            iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
            if (iSendLen <= 0)
            {
                close(iSocketClient);
                return -1;
            }
        }
    }
    
    return 0;
}

UDP

server.c

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>

/* socket
 * bind
 * sendto/recvfrom
 */

#define SERVER_PORT 8888
 
int main(int argc, char **argv)
{
    int iSocketServer;
    int iSocketClient;
    struct sockaddr_in tSocketServerAddr;
    struct sockaddr_in tSocketClientAddr;
    int iRet;
    int iAddrLen;

    int iRecvLen;
    unsigned char ucRecvBuf[1000];

    int iClientNum = -1;

    iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == iSocketServer)
    {
        printf("socket error!
");
        return -1;
    }

    tSocketServerAddr.sin_family       = AF_INET;
    tSocketServerAddr.sin_port     = htons(SERVER_PORT);  /* host to net, short */
    tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
    memset(tSocketServerAddr.sin_zero, 0, 8);

    iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
    if (-1 == iRet)
    {
        printf("bind error!
");
        return -1;
    }


    while (1)
    {
        iAddrLen = sizeof(struct sockaddr);
        iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
        if (iRecvLen > 0)
        {
            ucRecvBuf[iRecvLen] = '';
            printf("Get Msg From %s : %s
", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
        }
    }

    close(iSocketServer);
    return 0;
}

client.c

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>

/* socket
 * connect
 * send/recv
 */

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
    int iSocketClient;
    struct sockaddr_in tSocketServerAddr;
    
    int iRet;
    unsigned char ucSendBuf[1000];
    int iSendLen;

    if (argc != 2)
    {
        printf("Usage:
");
        printf("%s <server_ip>
", argv[0]);
        return -1;
    }

    iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);

    tSocketServerAddr.sin_family      = AF_INET;
    tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
    //tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
    if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
    {
        printf("invalid server_ip
");
        return -1;
    }
    memset(tSocketServerAddr.sin_zero, 0, 8);


    iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));    
    if (-1 == iRet)
    {
        printf("connect error!
");
        return -1;
    }

    while (1)
    {
        if (fgets(ucSendBuf, 999, stdin))
        {
            iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
            if (iSendLen <= 0)
            {
                close(iSocketClient);
                return -1;
            }
        }
    }
    
    return 0;
}

以上是关于第6课.网络编程的主要内容,如果未能解决你的问题,请参考以下文章

VFP+6.0中文版教程--初级教程

Redis2.6源代码走读第004课:字典的实现02

《英雄编程体验课》第 11 课 | 前缀和

Redis2.6源代码走读第007课:压缩列表01

Redis2.6源代码走读第007课:压缩列表02

Redis2.6源代码走读第004课:字典的实现03