美文网首页
网络通信

网络通信

作者: arkliu | 来源:发表于2022-12-05 11:35 被阅读0次

网络字节序

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort)
h表示host,n表示network,l表示32位长整数,s表示16位短整数。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,
如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

判断当前主机大小端

short tmp = 0x1234;
tmp = htons(tmp);
if ((*(char *)&tmp) == 0x34) {
    printf("It's little\n");
} else if ((*(char *)&tmp) == 0x12) {
    printf("It's big\n");
}

转换网络字节序为点分十进制的ip

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af 使用的协议
src  指向网络字节中要转换为字符串的IP地址的指针
dst  字符串缓冲区
size  缓冲区大小
返回值:若成功则为指向结构的指针,若出错则为NULL

将点分十进制ip转换成网络字节序

int inet_pton(int af, const char *src, void *dst);
af  使用的协议
src 指向"xxx.xxx.xxx.xxx"格式的字符串
dst 指向struct sockaddr_in.sin_addr.s_addr结构体的指针
返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1

端口复用

使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,
表示允许创建端口号相同但IP地址不同的多个socket描述符
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

socket函数

int socket(int domain, int type, int protocol);
domain:
AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
AF_INET6 与上面类似,不过是来用IPv6的地址
AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
type:
SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
protocol:
传0 表示使用默认协议。
返回值:
成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno

bind函数

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:
    socket文件描述符
addr:
    构造出IP地址加端口号
addrlen:
    sizeof(addr)长度
返回值:
    成功返回0,失败返回-1, 设置errno

struct sockaddr_in
{
   short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/
   unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/
   struct in_addr sin_addr;/*IP address in network byte order(Internet address)*/
   unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/
};

struct in_addr {
    in_addr_t s_addr;
};

listen函数

int listen(int sockfd, int backlog);
sockfd:
    socket文件描述符
backlog:
    排队建立3次握手队列和刚刚建立3次握手队列的链接数和
如果有大量的客户端发起连接而服务器来不及处理,尚未accept的客户端就处于连接等待状态,listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于待连接状态,如果接收到更多的连接请求就忽略

int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
sockdf:
    socket文件描述符
addr:
    传出参数,返回链接客户端地址信息,含IP地址和端口号
addrlen:
    传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
返回值:
    成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno

connect函数

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockdf:
    socket文件描述符
addr:
    传入参数,指定服务器端地址信息,含IP地址和端口号
addrlen:
    传入参数,传入sizeof(addr)大小
返回值:
    成功返回0,失败返回-1,设置errno  

shutdown函数

#include <sys/socket.h>
int shutdown(int sockfd, int how);
sockfd: 需要关闭的socket的描述符
how:    允许为shutdown操作选择以下几种方式:
SHUT_RD(0): 关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。
                该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。
SHUT_WR(1):     关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发出写操作。
SHUT_RDWR(2):   关闭sockfd的读写功能。相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。
使用close中止一个连接,但它只是减少描述符的引用计数,并不直接关闭连接,只有当描述符的引用计数为0时才关闭连接。
shutdown不考虑描述符的引用计数,直接关闭描述符。也可选择中止一个方向的连接,只中止读或只中止写。
注意:
1.  如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。 
2.  在多进程中如果一个进程调用了shutdown(sfd, SHUT_RDWR)后,其它的进程将无法进行通信。但,如果一个进程close(sfd)将不会影响到其它进程。
ssize_t send(int socket, const void *buffer, size_t length, int flags);
ssize_t sendmsg(int socket, const struct msghdr *message, int flags);
ssize_t sendto(int socket, const void *buffer, size_t length, int flags,
         const struct sockaddr *dest_addr, socklen_t dest_len);
成功返回发送的字节数,失败返回0

ssize_t recv(int socket, void *buffer, size_t length, int flags);

ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags,
struct sockaddr *restrict address, socklen_t *restrict address_len);

ssize_t recvmsg(int socket, struct msghdr *message, int flags);

server端和client端简单通信模型

server.c

1. socket()  建立套接字

2. bind() 绑定IP 端口号  (struct sockaddr_in addr 初始化)

3. listen() 指定最大同时发起连接数

4. accept()  阻塞等待客户端发起连接

5. read()

6. 小--大

7. write 给 客户端

8. close();
======================

client.c

1. socket();

2. bind();  可以依赖“隐式绑定”

3. connect();发起连接

4. write();

5. read();

6. close();

server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ctype.h>

#define IP "127.0.0.1"
#define PORT 8888

int main() {
    char ip_buf[1024];
    struct sockaddr_in ser_addr, cli_addr;
    int cfd, client_addr_len;
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        perror("server socket:");
        return -1;
    }
    ser_addr.sin_family = AF_INET;
    inet_pton(AF_INET, IP, &ser_addr.sin_addr.s_addr);
    ser_addr.sin_port = htons(PORT);
    bind(sockfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));

    listen(sockfd, 1024); //设置服务端允许的最大监听个数
    client_addr_len = sizeof(cli_addr);
    // cli_addr是一个传出参数,里面包含了客户端的连接信息
    cfd = accept(sockfd, (struct sockaddr *)&cli_addr, &client_addr_len); // 监听客户端连接,会阻塞
    // 下面打印客户端ip 要在linux下验证,mac下多一个参数
    //printf("client ip = %s clientport = ", inet_net_ntop(AF_INET, &cli_addr.sin_addr.s_addr, ip_buf, sizeof(ip_buf)));

    char buf[1024];
    int readCount;
    while (1)
    {
        readCount = read(cfd, buf, sizeof(buf));
        for (size_t i = 0; i < readCount; i++)
        {
            buf[i] = toupper(buf[i]);
        }
        write(cfd, buf, readCount);
    }
    close(cfd);
    close(sockfd);
}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ctype.h>

#define IP "127.0.0.1"
#define PORT 8888

int main() {
    struct sockaddr_in ser_addr;
    // 创建一个socket,指定使用IPV4 TCP协议
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd == -1)
    {
        perror("server socket:");
        return -1;
    }
    bzero(ser_addr, sizeof(ser_addr)); // 初始化并清零一个地址结构
    ser_addr.sin_family = AF_INET; //指定IPV4协议
    inet_pton(AF_INET, IP, &ser_addr.sin_addr.s_addr); //指定IP,字符串类型转换为网络字节序
    ser_addr.sin_port = htons(PORT); //指定端口
    // 连接到指定的服务器进程
    connect(sfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));

    char buf[1024];
    int count;
    while(1) {
        // 获取用户输入
        fgets(buf, sizeof(buf), stdin);
        // 将数据传递给服务器
        write(sfd, buf, strlen(buf));
        
        // 获取服务器返回的数据
        count = read(sfd, buf, sizeof(buf));
        // 写入到终端
        write(STDOUT_FILENO, buf, count);
    }
    close(sfd);
}

相关文章

网友评论

      本文标题:网络通信

      本文链接:https://www.haomeiwen.com/subject/mlimxdtx.html