socketsocket

Socket编程

2018-08-05  本文已影响8人  编程半岛

1. 什么是socket?

2. IPv4套接口

struct sockaddr_in {
    uint8_t sin_len;
  sa_family_t sin_family;
  in_port_t sin_port; /* 端口*/
  struct in_addr sin_addr; /* Internet 地址 */
  char sin_zero[8]; 
};

3. 通用的地址结构

struct sockaddr {
    uint8_t sin_len;
  sa_family_t  sa_family; /* 地址家族, AF_xxx */
  char sa_data[14]; /*14字节协议地址*/
};

4. 网络字节序

测试本机操作系统是大端还是小端:

#include <stdio.h>

using namespace std;

int main()
{
    unsigned int a = 0x12345678;

    unsigned char* p = (unsigned char*)&a;

    printf("%0x, %0x, %0x, %0x\n", p[0], p[1], p[2], p[3]);

    return 0;
}

5. 字节序转换函数

uint16_t htons(uint16_t hostshort)--"Host to Network Short"
uint32_t htonl(uint32_t hostlong)--"Host to Network Long"
uint16_t ntohs(uint16_t netshort)--"Network to Host Short"
uint32_t ntohl(uint32_t netlong)--"Network to Host Long"

6. 地址转换函数

点分十进制地址与32为整数地址转换
头文件:
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char* cp, struct in_add* inp);
in_addr_t inet_addr(const char* cp); // 将点分十进制地址转换为32位整数
char* inet_ntoa(struct in_addr in); // 将地址结构(32位整数)转换为点分十进制

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    unsigned long addr = inet_addr("192.168.0.100");    // 将点分十进制地址转换为32位整数 
    printf("addr = %u\n", ntohl(addr));                 // 将32位整数转化为网络字节序

    
    struct in_addr ipaddr;
    ipaddr.s_addr = addr;
    printf("%s\n", inet_ntoa(ipaddr));                  // 将地址结构(32位整数)转换为点分十进制
    
    return 0;
}

7. 套接字类型

8. TCP客户/服务器模型(C/S模型)

C/S模型

9. 回射客户/服务器模型

回射客户/服务器模型

10. socket函数

man帮助手册输入man socket即可获得帮助文档

    // 创建套接字
    int listenfd;
    if ( (listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))  < 0 )  // PF_INET(AF_INET):IPv4 Internet protocols, SOCK_STREAM:流式套接字
    {
        ERR_EXIT("socket");
    }

11. bind函数

man帮助手册输入man bind即可获得帮助文档

    // 地址的初始化
    struct sockaddr_in servaddr;    // IPv4的地址结构
    memset(&servaddr, 0, sizeof(servaddr)); // 初始化地址
    servaddr.sin_family = AF_INET;      // 地址族
    servaddr.sin_port = htons(5188);    // 指定端口号,并将端口号转化为2个字节的网络字节序
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);   // 绑定主机任意地址
    //servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");        // 指定地址  
    //inet_aton("127.0.0.1", &servaddr.sin_addr);       // 指定地址

    // 将套接字与地址绑定
    if( bind(listenfd, (struct sockaddr*)(&servaddr), sizeof(servaddr)) < 0 )
    {
        ERR_EXIT("bind");
    }

12. listen函数

调用listen函数后套接字变为被动套接字

    // 监听
    if( listen(listenfd, SOMAXCONN) < 0 )
    {
        ERR_EXIT("listen");
    }

13. accept函数

    // 接收
    struct sockaddr_in peeraddr;    // 对方的地址
    socklen_t peerlen = sizeof(peeraddr);
    int conn;       // 定义已连接套接字
    if( accept(listenfd, (struct sockaddr*)(&peeraddr), &peerlen) < 0 )
    {
        ERR_EXIT("accept");
    }

14. connect函数

    // 连接 connect
    if( connect(sock, (struct sockaddr*)(&cliaddr), sizeof(cliaddr))  < 0 )
    {   
        ERR_EXIT("connect");
    }

15. REUSEADDR

    // 设置地址重复利用 REUSEADDR
    int on = 1;
    if( (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))  < 0) )
    {
        ERR_EXIT("setsockopt");
    } 

16. process-per-connection

    // 通过多进程来使得服务器可以接收多个客户端的消息,从而达到并发效果
    // 父进程用来监听,子进程用来处理通信
    pid_t pid;
    while(true)
    {
        if( (conn = accept(listenfd, (struct sockaddr*)(&peeraddr), &peerlen)) < 0 )
        {
            ERR_EXIT("accept");
        }

        printf("ip=%s, port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); // 打印客服端发送过来的ip和port

        pid = fork();
        if( pid == -1 )
        {
            ERR_EXIT("fork");
        }
        else if( pid == 0 )     // 子进程来处理通信
        {
            close(listenfd);    // 子进程不需要监听,将监听套接口关闭
            do_servece(conn);   // 通信处理函数
            exit(EXIT_SUCCESS); // 当客户端关闭后,子进程销毁
        }
        else    // 父进程
        {
            close(conn);    // 父进程不需要通信,将通信套接口关闭
        }
    }

// 通信处理函数
void do_servece(int conn)
{
    char recvbuf[1024];
    while(true)
    {
        memset(recvbuf, 0, sizeof(recvbuf));
        int ret = read(conn, recvbuf, sizeof(recvbuf));
        if( ret == 0 )          // 返回值为0,表示客户端关闭
        {
            printf("Client close");
            break;              // 当客户端关闭,退出循环
        }
        else if( ret == -1)
        {
            ERR_EXIT("read");
        }
        fputs(recvbuf, stdout);
        write(conn, recvbuf, ret);
    }
}

17. 点对点的聊天程序实现

18. 流协议与粘包问题

19. 粘包产生的原因

20. 粘包处理方案

21. readn 和 writen

22. 完善回射客户/服务器程序

上一篇下一篇

猜你喜欢

热点阅读