Linux 网络编程——Socket编程基础

1.什么是socket?

2.socket基本操作

需要的头文件

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>

2.1 socket()

int socket(int domain, int type, int protocol); 

DESCRIPTION

  • domain: 协议族,常见的协议族AF_INET, AF_INET6等
  • type: 指定socket类型,SOCK_STREAM(提供有序,可靠,双向的基于连接的字节流), SOCK_DGRAM(支持数据报(固定最大长度的无连接、不可靠消息))等
  • protocol: 指定协议, 常用的协议有IPPROTO_TCP、IPPTOTO_UDP等

注意:type和protocol不能随意组合,例如: SOCK_STREAM不可以和IPPTOTO_UDP组合,当protocol为0时,会自动选择type类型对应的默认协议.

2.2 bind()

当一个socket被socket() 创建出后,它存在于命名空间(address family)中,但是没有一个具体的地址。bind() 将 addr 指定的地址分配给文件描述符 sockfd 引用的套接字。如果没有绑定, 在后面调用connect(), listen()时, 系统会自动随机分配一个端口。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

DESCRIPTION

  • sockfd: socket描述符,由socket()唯一创建
  • addr: const struct sockaddr *的指针,指向绑定给sockfd的协议地址,由于一些历史问题,通常该指针指向一个sockaddr_in类型的结构体(IPv4)
  • addrlen: 指定 addr 指向的地址结构的大小(以字节为单位)
struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};
 
/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

网络字节序和主机字节序(在另一篇文章详细讲解)
数据在传输过程中,例如从主机a到主机b进行传输信息,会经历以下三步:
a的主机字节序->网络字节序-->b的主机字节序
主机字节序 :主机内部,内存中数据的存储方式,引用标准的Big_Endian(大端)和Little_Endian(小端)的定义如下:
(1) Big_Endian: 高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
(2) Little_Endian: 低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
网络字节序 :网络字节顺序是TCP/IP中规定好的一种数据表示格式,采用Big-endian(大端)排序方式。
因此,在将sockaddr_in地址绑定给sockfd前,如果主机字节序是小端存储,请先将主机字节序转化为网络字节序.

通常服务器会在listen()之前,通过bind()绑定一个暴露给客户端的地址(IP地址+端口号),客户端可以通过这个暴露的地址建立连接获取服务;而客户端不需要通过bind()确定一个指定地址,而是在connet()时候系统随机分配一个

2.3 listen()/connect()

int listen(int sockfd, int backlog);

DESCRIPTION

  • sockfd: 文件描述符,它引用 SOCK_STREAM 或 SOCK_DGRAM 类型的套接字。
  • backlog: backlog定义了 sockfd 的挂起连接队列可以增长到的最大长度。如果连接请求在队列已满时到达,客户端可能会收到带有 ECONNREFUSED 指示的错误,或者,如果底层协议支持重传,则可能会忽略该请求,以便稍后重新尝试连接成功。

作为Server,在调用了bind()函数后,就应该对sockfd进行监听,listen()函数将默认主动的socket变为被动类型的,等待客户端的连接请求。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

DESCRIPTION

  • sockfd: 客户端的socket描述符
  • addr: 服务器的socket地址
  • addrlen: socket地址的长度
    客户端通过connect()函数与服务器建立TCP连接

2.4 accept()

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

DESCRIPTION

  • 和bind()参数一致

侦听套接字提取挂起连接队列中的第一个连接请求,sockfd,创建一个新的连接套接字,并返回一个引用该套接字的新文件描述符。 新创建的套接字不处于监听状态。 原始套接字 sockfd 不受此调用的影响。
RETURN VALUE

  • 成功时,这些系统调用返回一个非负整数,它是接受套接字的文件描述符。
  • 出错时,返回 -1,适当设置 errno,并且 addrlen 保持不变。

TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

2.5 send(),recv()等I/O函数

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// 只能在套接字处于连接状态时使用(以便知道预期的接收者)

DESCRIPTION

  • sockfd: 要发送给的哪个socket描述符
  • buf: 发送的消息
  • len: 消息大小,如果消息太长而无法原子地通过底层协议,则返回错误 EMSGSIZE,并且不传输消息。
  • flags: 标识参数,使用0标识参数,send()等效于write()
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

DESCRIPTION

  • 参数和send()一致

2.6 close()

int close(int fd);

关闭文件描述符,使其不再引用任何文件并且可以重用。

2.socket编程demo

  • Server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>  // socket套接字
#include <unistd.h>
#include <arpa/inet.h>  // 主机字节序和网络字节序转化

#define _SERVER_PORT 8080
#define BACK_LOG 128
#define IP_SIZE 16
#define BUFFER_SIZE 1600

int server_net_init(void) {
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(_SERVER_PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //设置本机任意IP
 
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind error");
        exit(-1);
    }
    listen(server_fd, BACK_LOG); // 将默认主动的socket变为被动类型的
    printf("TCP Server Waiting for Connect ...\n");

    return server_fd;
}

int server_accepting(int sockfd) {  // 阻塞模型,阻塞等待客户端连接
    struct sockaddr_in client_addr;
    socklen_t addrlen = sizeof(client_addr);
    int client_fd;
    char client_ip[IP_SIZE];
    bzero(client_ip, IP_SIZE);

    if ((client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen)) > 0) { // accept 阻塞函数
        printf("TCP Server Accept Success:client ip[%s] client prot[%d] \n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip, IP_SIZE),ntohs(client_addr.sin_port));
    } else {
        perror("Accept Call Failed");
        exit(-1);
    }
    return client_fd;
}

int server_recv_response(int sockfd) {
    char recv_buffer[BUFFER_SIZE];
    bzero(recv_buffer, BUFFER_SIZE);
    ssize_t recvlen;

    while ((recvlen = recv(sockfd, recv_buffer, sizeof(recv_buffer), 0)) > 0) {
        printf("%s", recv_buffer);
        send(sockfd, recv_buffer, recvlen, 0);// 回传
        bzero(recv_buffer, BUFFER_SIZE);
    }
    if (recvlen == -1) {
        printf("recv error");
        exit(-1);
    }
    return 0;
}

int main(void) {
    int sfd = server_net_init();      // 服务端网络初始化
    int cfd = server_accepting(sfd);  // 阻塞等到连接

    server_recv_response(cfd);

    close(cfd);
    close(sfd);

    return 0;
}
  • Client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>  // socket套接字
#include <unistd.h>
#include <arpa/inet.h>  // 大小端转换

#define _SERVER_PORT 8080
#define _SERVER_IP "172.24.126.207"
#define BUFFER_SIZE 1600

int main(void) {
    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(_SERVER_PORT);
    inet_pton(AF_INET, _SERVER_IP,&server_addr.sin_addr.s_addr);  // 设置服务器IP
    int cfd = socket(AF_INET, SOCK_STREAM, 0);
    
    int reval;
    ssize_t recvlen;
    char buffer[BUFFER_SIZE];
    bzero(buffer, BUFFER_SIZE);
    
    if ((reval = connect(cfd, (struct sockaddr *)&server_addr, sizeof(server_addr))) == 0) {
        printf("Client Connection Server Success ... \n");

        while ((fgets(buffer, sizeof(buffer), stdin)) != NULL) {
            send(cfd, buffer, strlen(buffer), 0);
            recvlen = recv(cfd, buffer, sizeof(buffer), 0);
            write(STDOUT_FILENO, buffer, recvlen);
            bzero(buffer, BUFFER_SIZE);
        }
    } else if (reval == -1) {
        perror("Connection Failed");
        exit(-1);
    }
    return 0;
}

posted on 2022-04-27 17:48  SocialistYouth  阅读(77)  评论(0编辑  收藏  举报