Linux之socket编程

本文详细解释了服务器和客户端在建立TCP连接时的7个服务端步骤(socket、bind、listen、accept、recv、send、close)以及5个客户端步骤(socket、connect、send、recv、close),涉及AF_INET、SOCK_STREAM、IP地址转换等关键概念。
摘要由CSDN通过智能技术生成

        这是服务器和客户端的连接流程,服务端主要进行7个步骤。分别是创建服务器socket()、设置ip和端口bind()、监听listen()、接收客户端accept()、读取数据recv()、发送数据send()、关闭服务器close()。

        客户端主要进行5个步骤, 创建服务器socket()、连接服务器connect()、发送数据send()、接收数据recv()数据、关闭服务器close()。

服务端

socket

#include <sys/types.h>          /* See NOTES */  

#include <sys/socket.h>

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

domain

AF_INET             IPv4 Internet protocols          ip(7)
AF_INET6           IPv6 Internet protocols          ipv6(7) 

        domain选项通常选这两个,这里选IPv4的AF_INET。

type

SOCK_STREAM      Provides sequenced, reliable, two-way, connection-based
                                 byte  streams.  An out-of-band data transmission mecha‐
                                 nism may be supported.

        type选SOCK_STREAM这个流,protocol给0就好,让系统帮我们选择。

s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1)
{
        perror("socket");
        exit(-1);
}

        socket的返回值是int类型,这里用s_fd来接收并判断socket是否创建成功。s_fd将作为客户端的描述符。 

bind

 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>

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

        sockfd就是客户端的表述符。

struct sockaddr *addr

struct sockaddr_in {
  __kernel_sa_family_t          sin_family;        /* Address family               */
  __be16                                sin_port;           /* Port number                  */
  struct in_addr                      sin_addr;          /* Internet address             */
};

        bind()函数的第二个参数是struct sockaddr这样一个结构体,我们用struct sockaddr_in来代替,之后用强制转换把数据类型转换回去就好了。

        sin_family是协议类型,这里和socket()函数一样,选择IPv4----AF_INET。sin_port是端口号,例如8888或者5555之类的。sin_addr也是结构体定义的变量,这里代表IP地址。

struct in_addr {
        __be32  s_addr;
}; 

        两台主机想要进行网络通讯,先规定好协议类型,然后选择端口号,最后选择IP地址,这样才能通讯。一般来说,我们输入的IP地址电脑是看不懂的,这时候需要使用转换字节序inet_aton()函数,把主机字节序转换成网络字节序。同时也可以使用inet_ntoa()函数把网络字节序转换成主机字节序。

#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);

char *inet_ntoa(struct in_addr in);

        cp是IP地址号,例如192.168.1.10等等。inp是把IP地址转换到的结构体,in是转成主机字节序的结构体。

        bind()函数的最后一个参数是地址的大小。

struct sockaddr_in s_addr;

s_fd = socket(AF_INET, SOCK_STREAM, 0);

        最后再介绍一下htons()函数。

#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);

        介绍这个函数之前,我们要知道主机字节数有两个模式,一个是大端模式,一个是小端模式。小端模式就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。大端模式1就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

        我们电脑的主机字节序一般情况下是小端的,而网络字节序是大端的,所以我们需要进行转换,不然会发生错误。htons()函数的作用就是将小端模式转换成大端模式。

        bind()函数有配置的内容就三个,协议类型、端口号和IP地址,IP地址会比较麻烦。

listen

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

        listen()比较简单,sockfd是服务端表述符,backlog是监听个数。

listen(s_fd,10);

accept

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

        sockfd是服务端描述符。struct sockaddr *addr和bind()的是一致的,最后一个是长度,传递的是指针。

​struct sockaddr_in c_addr;

int len = sizeof(struct sockaddr_in);
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&len);

        accept()的返回值也是int类型,定义一个c_fd变量来接收它的返回值,也可以称为表述符。 

read

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

        fd是表述符,这里比较推荐写accept()的表述符。buf是读入哪个数组,最后一个是读入的大小。

char readbuf[128];

n_read = read(c_fd,readbuf,128);

write

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count); 

        write()和read()没什么区别。

char *msg = "hello  world";

write(c_fd,msg,strlen(msg));

close

#include <unistd.h>

int close(int fd);

        close()函数也非常简单。

 close(c_fd);

服务端总体代码

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

int main()
{
        int s_fd;
        int c_fd;
        int n_read;

        char readbuf[128];
        char *msg = "hello  world";

        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;
        //socket
        s_fd = socket(AF_INET, SOCK_STREAM, 0);
        if(s_fd == -1)
        {
                perror("socket");
                exit(-1);
        }
        //bind
        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(8988);
        inet_aton("127.0.0.1",&s_addr.sin_addr);

        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
        //listen
        listen(s_fd,10);
        //accept
        int len = sizeof(struct sockaddr_in);
        c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&len);
        if(c_fd == -1)
        {
                perror("accept");
                exit(-2);
        }
        printf("accept success , ip = %s\n",inet_ntoa(c_addr.sin_addr));
        //read
        n_read = read(c_fd,readbuf,128);
        if(n_read == -1)
        {
                perror("read");
                exit(-3);
        }
        else
        {
                printf("read %d byte from client : %s\n",n_read,readbuf);
        }
        //write
        write(c_fd,msg,strlen(msg));

        close(c_fd);

        return 0;
}

客户端

客户端的代码量比较少,先是创建服务器。

socket

struct sockaddr_in c_addr;​

c_fd = socket(AF_INET, SOCK_STREAM, 0);

if(c_fd == -1)
{
        perror("socket");
        exit(-1);
}

c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(8988);
inet_aton("127.0.0.1",&c_addr.sin_addr);

​

        虽然bind()函数不需要,但是协议、端口号、IP地址还是要配置的。 

connect

 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>

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

        connect()的参数和bind()的一致,搬过来用就好了。

struct sockaddr_in c_addr;

if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1)
{
        perror("connect");
        exit(-2);
}

write

        和服务端不同的是,客户端需要先写再读。

char *msg = "hello world to";

write(c_fd,msg,strlen(msg));

read

char readbuf[128] = {0};

n_read = read(c_fd,readbuf,sizeof(readbuf));

close

 close(c_fd);

客户端总代码

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

int main()
{
        int c_fd;
        int n_read;

        char readbuf[128] = {0};
        char *msg = "hello  world to";

        struct sockaddr_in c_addr;
        //socket
        c_fd = socket(AF_INET, SOCK_STREAM, 0);
        if(c_fd == -1)
        {
                perror("socket");
                exit(-1);
        }
        c_addr.sin_family = AF_INET;
        c_addr.sin_port = htons(8988);
        inet_aton("127.0.0.1",&c_addr.sin_addr);

        //connect
        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1)
        {
                perror("connect");
                exit(-2);
        }
        //write
        write(c_fd,msg,strlen(msg));

        //read
        n_read = read(c_fd,readbuf,sizeof(readbuf));
        if(n_read == -1)
        {
                perror("read");
                exit(-3);
        }
        else
        {
                printf("read %d byte from client : %s\n",n_read,readbuf);
        }

        close(c_fd);

        return 0;
}

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

六花不会哭T﹏T

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值