网络编程总结(2)

一、网络编程

        套接字:传输层的文件描述符

1.1TCP的C/S实现

        1.1.1TCP服务器实现过程

        创建套接字socket函数

        调用bind函数给套接字绑定IP地址和端口号

        将套接字文件描述符从主动变为被动文件,调用listen函数开始监听

        通过调用accept函数被动监听客户的链接并响应

        服务器调用read(recv)和write(send),收发数据,实现与客户端的通信

        调用close或者shutdown关闭TCP的链接

        (1)创建套接字socket函数

头文件:#include <sys/types.h>
       #include <sys/socket.h>

原型: int socket(int domain, int type, int protocol);

功能: 创建套接字,返回一个文件描述符
参数:
       domian:通信域
            AF_UNIX, AF_LOCAL   Local communication              unix(7)   //本地通信
            AF_INET             IPv4 Internet protocols          ip(7)    //ipv4网络协议
            AF_INET6            IPv6 Internet protocols          ipv6(7)   //ipv6网络协议
            AF_IPX              IPX - Novell protocols
            AF_NETLINK          Kernel user interface device     netlink(7)
            AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)
            AF_AX25             Amateur radio AX.25 protocol
            AF_ATMPVC           Access to raw ATM PVCs
            AF_APPLETALK        AppleTalk                        ddp(7)
            AF_PACKET           Low level packet interface       packet(7)   //底层协议通信
            AF_ALG              Interface to kernel crypto API
        type:套接字类型
            SOCK_STREAM   :   流式套接字    ------>tcp
            SOCK_DGRAM    :   数据包套接字  ------>udp
            SOCK_RAW      :   原始套接字
        protocol:附加协议,传0便是不需要其它协议
返回值:
        成功:返回套接字文件描述符(sockfd)
        失败:返回-1,errno被设置

        (2)调用bind函数给套接字绑定IP地址和端口号   

头文件:  #include <sys/types.h>
         #include <sys/socket.h>
原型:    int bind(int socked, const struct sockaddr *addr, socklen_t addrlen);

功能:    将套接字与网络信息结构体绑定
参数:
         sockfd:文件描述符,socket的返回值
         addr:保存IP和端口号的网络信息结构体
         通用结构体:结构体的长度,一般不用
            struct sockaddr{
                    sa_family_t sa_family;
                    char        sa_data[14];
         网路信息结构体:
            struct sockaddr_in{
                    sa_family_t sin_family;      //地址族:AF_INET
                    in_port_t   sin_port;        //网络字节序的端口号
                    struct in_addr sin_addr      //ip地址
                            struct in_addr
                            {
                                uint32_t s_addr;   //网络字节序的无符号4字节整数ip地址
                            }
            }
            addrlen:addr的大小
返回值:
        成功:返回0
        失败:返回-1,errno被设置

        出现无法绑定的情况

        client终止时自动关闭socket描述符,server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。MSL在RFC1122中规定为两分钟;

        解决方法:setsockopt函数

        在server代码的socket()和bind()调用之间插入如下代码:

int opt = 1;
setsocket(socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

        (3)将套接字文件描述符,从主动变为被动文件描述符(做监听准备):listen

头文件: #include <sys/types.h>
        #include <sys/socket.h>

原型:int listen(int socked, int backlog);

功能:将套接字设置为被动监听状态

参数:
    sockfd:文件描述符,socket的返回值
    backlog:允许同时连接的客户端的个数,一般设置为5,10,一般小于30

返回值:
    成功:0
    失败:-1,errno被设置
        

        (4)被动监听客户的连接并响应:accept函数

头文件:    #include <sys/types.h>
          #include <sys/socket.h>

原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

功能:被动监听客户发起三次握手的链接请求,三次握手成功,阻塞等待客户端的连接

参数:
    sockfd:文件描述符,socket的返回值
    addr:被填充的网络信息结构体,如果由客户端连接服务器,服务器可以通过这个参数获取客户端的信息。
    addrlen:addr的大小

返回值:
    成功:返回一个通信描述符,专门用于该连接成功的客户的通信,总之后续服务器与该客户间正式通信,使用的就是accept返回的“通信描述符”来实现的
    失败:-1,errno被设置

        (5)服务器调用read(recv)和write(send),收发数据,实现与客户的通信

        send函数

头文件:#include <sys/types.h>
       #include <sys/socket.h>

原型:size_t send(int socked, const void *buf, size_t len, int flags);

功能:向对方发送数据

参数:
    参数1:用于通信的通信描述符
    参数2:应用缓存,用于存放要发送的数据
    参数3:buf缓存的大小
    参数4:
        设置为0,send阻塞发送的数据
        设置MSG_NOSIGNAL:send数据时,若对方关闭链接调用send的进程便发送SIGPIEIE信号
        设置MSG_DONTWAIT:非阻塞发送
        设置MSG_OOB:表示发送的是带外数据

返回值:
    成功:返回发送的字节数
    失败:-1

        recv函数 

头文件:#include <sys/types.h>
       #include <sys/socket.h>

原型:size_t recv(int sockfd, void *buf, size_t len, int flags);

功能:接收对方发送的数据

参数:
    参数1:通信文件描述符
    参数2:应用缓存,用于存放接收的数据
    参数3:buf缓存的大小
    参数4:
        设置为0,recv阻塞接收
        设置MSG_DONTWAIT:非阻塞接收
        设置MSG_OOB:表示接收的是带外数据

返回值:
    成功:返回文件描述符
    失败:-1

        (6)调用close或者shutdown关闭TCP的链接  

        close

                缺点1:会一次性将读写都关掉了

                缺点2:如果多个文件描述符指向了同一个链接时,如果只close关闭其中某个文件描述符时,只要其它的fd还打开着,那么链接不会被断开,直到所有的描述符都被close后才断开链接

        出现多个描述指向同一个链接的原因可能两个:

                1.通过dup方式复制出其它描述符

                2.子进程继承了这个描述符,所以子进程的描述符也指向了链接

        shutdown

头文件:#include <sys/socket.h>

原型:int shutdown(int socked,int how);


功能:可以按照要求关闭链接,而且不管有多少个描述符指向同一个链接,只要调用shutdown去操作了其中某个描述符,链接就会立即断开

参数:
    参数1:TCP服务器断开连接时,使用的是accept所返回的文件描述符
    参数2:如何断开链接
        SHUT_RD:只断开读链接
        SHUT_WR:只断开写链接
        SHUT_RDWR:读写链接都断开
返回值:
    成功:0
    失败:-1

         1.1.2 TCP客户端的实现过程

        使用socket创建套接字文件,指定TCP文件

        调用connect函数自动向服务器发起上次握手,进行链接

        调用read/recv和write/send,来进行收发数据

        调用close/shutdown关闭TCP的链接

        (1)用socket创建套接字文件,指定使用TCP协议

        (2)调用connect主动向服务器发起三次握手,进行连接

头文件:#include <sys/types.h>         
        #include <sys/socket.h>      
               
      
原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能:向服务器主动发起连接请求

参数:
    参数1:socket所返回的套接字文件描述符
    参数2:用于设置你所要连接服务器的IP和端口
    参数3:参数2所指定的结构体变量的大小
返回值:
    成功:0
    失败:-1

        (3)调用read/recv和write/send,来进行收发数据

        send函数

头文件:#include <sys/types.h>
        #include <sys/socket.h>
             
      
原型:size_t send(int sockfd, const void *buf, size_t len, int flags);

功能:向对方发送数据

参数:
    参数1:用于通信的通信描述符
    参数2:应用缓存,用于存放你要发送的数据
    参数3:buf缓存的大小
    参数4:
        设置为0,send阻塞发送
        设置MSG_NOSIGNAL:send数据时,若对方关闭链接调用send的进程便发送SIGPIEIE信号
        设置MSG_DONTWAIT:非阻塞发送
        设置MSG_OOB:表示发送的是带外数据

返回值:
    成功:返回发送的字节数
    失败:-1

         recv函数

头文件:#include <sys/types.h>
        #include <sys/socket.h>
             
      
原型:size_t recv(int sockfd, void *buf, size_t len, int flags);

功能:接收对方发送的数据

参数:
    参数1:通信文件描述符
    参数2:应用缓存,用于存放接收的数据
    参数3:buf缓存的大小
    参数4:
        设置为0,recv阻塞接收
        设置MSG_DONTWAIT:非阻塞接收
        设置MSG_OOB:表示接收的是带外数据

返回值:
    成功:返回文件描述符
    失败:-1

        (4)调用close或者shutdown关闭链接

        close:

                缺点1:会一次性将读写都关掉

                缺点2:如果多个文件描述符指向同一个链接时,如果只close关闭其中某个文件描述符时,只要其它的fd还打开着,那么链接就不会断开,直到所有的描述符都被close后才断开链接。

                出现多个描述符指向同一个链接的原因可能有两个:

        1.通过dup方式复制出其它描述符

        2.子进程继承了这个描述符,所以子进程的描述符也指向了链接

        shutdown

头文件:#include <sys/socket.h>
             
      
原型:int shutdown(int sockfd, int how);

功能:可以按照要求关闭连接,而且不管有多少个描述符指向同一个连接,只要调用shutdown去操作了其中某个描述符,连接就会被立即断开

参数:
    参数1:TCP服务器断开连接时,使用的是accept所返回的文件描述符
    参数2:如何断开链接
        SHUT_RD:只断开读连接
        SHUT_WR:只断开写连接
        SHUT_RDWR:读、写连接都断开

返回值:
    成功:返回0
    失败:-1

        1.1.3 TCP代码实现

        TCP服务器

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include<arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int main(int argc, char const *argv[])
{
    //创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);  //IPV4协议,流式套接字,具体的协议类型
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }
    struct sockaddr_in server_addr;   //保存服务器的信息
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8000);
    server_addr.sin_addr.s_addr = inet_addr("192.168.98.147");
    //绑定信息
    int ret = bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    if(-1 == ret)
    {
        perror("bind");
        return -1;
    }


    //设置监听队列
    ret = listen(sockfd,10);
    if(-1 == ret)
    {
        perror("listen");
        return -1;
    }
    printf("等待客户端进行连接...、\n");
    struct sockaddr_in Client_addr;    //用于保存客户端的信息
    int length = sizeof(Client_addr);
    int fd = accept(sockfd,(struct sockaddr *)&Client_addr,&length);
    if(-1 == fd)
    {
        perror("accept");
        return -1;
    }
    printf("接收客户端的连接 %d\n",fd);
    char buf[32] = {0};
    while(1)
    {
        ret = recv(fd,buf,sizeof(buf),0);
        if(-1 == ret)
        {
            perror("recv");
            return -1;
        }
        if(strcmp(buf,"bye") == 0)
        {
            break;
        }
        printf("%s\n",buf);
        memset(buf,0 ,sizeof(buf));
    }
    close(fd);
    close(sockfd); 
    return 0;
}

        TCP客户端

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


int main(int argc, char const *argv[])
{
    //创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        perror("socket");
        return -1;
    }
    //向服务器发起连接
    struct sockaddr_in server_addr;   //保存服务器的信息
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8000);
    server_addr.sin_addr.s_addr = inet_addr("192.168.98.147");
    int ret = connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    if(-1 == ret)
    {
        perror("connect");
        return -1;
    }
    char buf[32] = {0};
    while(1)
    {
        scanf("%s",buf);
        ret = send(sockfd,buf,strlen(buf),0);
        if(-1 == ret)
        {
            perror("send");
            return -1;
        }
        if(strcmp(buf,"bye") == 0)
        {
            break;
        }
        memset(buf,0,sizeof(buf));
    }
    close(sockfd);
    return 0;
}

        2.1.4TCP并发多任务服务器

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include<arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <pthread.h>
void * ClientHandler(void *arg)
{
    int ret;
    int fd = *(int *)arg;
    char buf[32] = {0};
    pthread_detach(pthread_self());    //线程结束,自动释放资源
    while(1)
    {
        ret = recv(fd,buf,sizeof(buf),0);
        if(-1 == ret)
        {
            perror("recv");
            return (void *)-1;
        }
        else if(0 == ret)
        {
            break;   //客户端异常退出
        }
        if(strcmp(buf,"bye") == 0)
        {
            break;
        }
        printf("接收%d客户端 %s\n",fd,buf);
        memset(buf,0 ,sizeof(buf));
    }
    printf("%d 客户端退出!\n",fd);
    close(fd);


}
int main(int argc, char const *argv[])
{
    //创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);  //IPV4协议,流式套接字,具体的协议类型
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }
    struct sockaddr_in server_addr;   //保存服务器的信息
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8000);
    server_addr.sin_addr.s_addr = inet_addr("192.168.98.147");
    //绑定信息
    int ret = bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    if(-1 == ret)
    {
        perror("bind");
        return -1;
    }


    //设置监听队列
    ret = listen(sockfd,10);
    if(-1 == ret)
    {
        perror("listen");
        return -1;
    }
    printf("等待客户端进行连接...、\n");
    struct sockaddr_in Client_addr;    //用于保存客户端的信息
    int length = sizeof(Client_addr);
    
    while(1)
    {
        int fd = accept(sockfd,(struct sockaddr *)&Client_addr,&length);
        if(-1 == fd)
        {
            perror("accept");
            return -1;
        }
        printf("接收客户端的连接 %d\n",fd);
        //为每一个客户端创建新的线程
        pthread_t tid;
        ret = pthread_create(&tid,NULL,ClientHandler,&fd);
        if(ret != 0)
        {
            perror("pthread_create");
            return -1;
        }
    }
    close(sockfd); 
    return 0;
}

1.2 UDP的C/S实现

        1.2.1 特点:

        UDP协议没有建立连接特性,所以UDP协议没有自动记录对方IP和端口的特点,每次发送数据时,必须亲自指定对方的IP和端口,只有这样才能将数据发送给对方

        1.2.2 UDP通信过程

        调用socket创建套接字文件

        bind绑定固定的ip和端口

        调用sendto和recvfrom函数,发送和接收数据

        sendto

头文件:#include <sys/types.h>
       #include <sys/socket.h>
             
      
原型:size_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

功能:当后两个参数为NULL和0时,功能等价于send,send专门用于TCP这种面向连接的通信,但对于像UDP这种非连接的通信,必须使用sendto,因为此时必须使用最后两个参数

参数:
    参数1:socket返回的套接字描述符
        对于UDP来说,套接字描述符直接用于通信
    参数2:存放数据的应用缓存
    参数3:应用缓存的大小
    参数4:一般写0,表示阻塞发送数据
    参数5:写目标ip和端口
    参数6:dest_addr的大小

返回值:
    成功:返回发送的字节数
    失败:-1

        recvfrom

头文件:#include <sys/types.h>
       #include <sys/socket.h>
             
      
原型:size_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr*src_addr, socklen_t *addrlen);

功能:收数据,最后两个参数与NULL和NULL时,功能与recv功能相同UDP通信时,常使用的是recvfrom函数,因为需要用到后两个参数

参数:
    参数1:socket返回的套接字文件描述符
    参数2:应用缓存,用于存放接收到的数据
    参数3:buf的大小
    参数4:一般写0,表示阻塞接收数据
    参数5:用于保存“数据发送方”的ip和端口

返回值:
    成功:返回接收到的字节数
    失败:-1

         1.2.3广播

        一个人发,然后其它所有人都接收,则就是广播

        广播只能在局域网内部有效,广播数据是无法越过路由器的,也就是路由器就是广播数据的边界

        实现方法:IP地址协会曾广播地址; 例如:192.168.1.255

                        接收端的IP地址不能设置为固定IP,要指定为INADDR_ANY

        1.2.4组播 

        广播是给其它所有计算机广播数据,而组播只对其它所有计算机中的某部分计算机广播数据

        1.2.5UDP网络编程代码

        UDP服务器

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n",__FILE__,__func__,__LINE__);\
                            exit(1);\
                            }while(0)
int main(int argc, char const *argv[])
{
    int sockfd;
    //第一步:创建套接字
    if(-1 == (sockfd = socket(AF_INET,SOCK_DGRAM,0)))
    {
        ERRLOG("socket error");
    }
    
    struct sockaddr_in serveraddr,clientaddr;
    socklen_t addrlen = sizeof(serveraddr);


    //第二步:填充服务器网络信息结构体  
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr("192.168.98.147");
    serveraddr.sin_port = 8888;


    //第三步:将套接字与服务器网路信息结构体绑定
    int ret = bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
    if(ret == -1)
    {
        ERRLOG("bind");
    }
    //进行通信
    char buf[32] = {0};
    while(1)
    {
NEXT:
        if(recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&clientaddr,&addrlen) == -1)
        {
            ERRLOG("recvfrom");
        }
        if(strcmp(buf,"bye") == 0)
        {
            printf("客户端%s-%d退出了\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
            goto NEXT;
        }
        printf("%s-%d: %s\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port),buf);
    }
    return 0;
}

        UDP客户端

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n",__FILE__,__func__,__LINE__);\
                            exit(1);\
                            }while(0)
int main(int argc, char const *argv[])
{
    int sockfd;
    //第一步:创建套接字
    if(-1 == (sockfd = socket(AF_INET,SOCK_DGRAM,0)))
    {
        ERRLOG("socket error");
    }
    
    struct sockaddr_in serveraddr,clientaddr;
    socklen_t addrlen = sizeof(serveraddr);


    //第二步:填充服务器网络信息结构体  
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr("192.168.98.147");
    serveraddr.sin_port = 8888;


    //进行通信
    char buf[32] = {0};
    while(1)
    {
        scanf("%s",buf);
        if(sendto(sockfd,buf,32,0,(struct sockaddr *)&serveraddr,addrlen) == -1)
        {
            ERRLOG("sendto");
        }
        if(strcmp(buf,"bye") == 0)
        {
            printf("客户端退出了\n");
        }
        memset(buf,0,sizeof(buf));
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值