当前位置:   article > 正文

Socket网络编程基础

socket网络编程

转载自 嵌入式 Linux网络编程(一)——Socket网络编程基础

一、Socket简介

1. 网络中进程间通信

本机进程所使用进程号来区分不同的进程。进程间的通信方式有管道、信号、消息队列、共享内存、信号量等。
而网络中进程间通信,首先需要知道进程所在的主机,即网络中唯一的标识即网络层的IP地址,主机上的进程可以通过传输层的协议和端口号识别。

2. Socket原理

Socket是应用层与TCP/IP 协议族通信的 中间软件抽象层,是一种编程接口。Socket屏蔽了不同网络协议的差异支持面向连接(TCP)和无连接(UDP)。
在这里插入图片描述

二、Socket 通信的基础知识

2.1 网络字节序

字节序在内存中存储的方式有大端序(网络序)和小端序(主机序)。
大端序:将高字节存储在低地址。
小端序:将低字节存储在低地址。
网络中在处理多字节时一般采用大端序,在网络传输中需要把主机字节序转换到网络字节序。常用的的转换函数如下:

#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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.2 数据结构

struct sockaddr {
    sa_family_t sa_family; /* address family, AF_xxx */
    char sa_data[14]; /* 14 bytes of protocol address */
};

struct sockaddr_in {
     __kernel_sa_family_t sin_family; /* Address family */
     __be16 sin_port; /* Port number */
     struct in_addr sin_addr; /* Internet address */
     /* Pad to size of `struct sockaddr'. */
     unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
	 sizeof(unsigned short int) - sizeof(struct in_addr)];
};

/* Internet address. */
struct in_addr {
    __be32 s_addr;
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2.3 IP地址转换

//将str指向的字符串IP地址转换为32位的网络字节序IP地址
int inet_aton( const* str, struct int_addr *inp);
//将str指向的字符串IP地址转换为32位的网络字节序并返回
int_addr_t inet_addr( const* str);
//将32位的网络字节序IP地址转换为点分十进制的字符串IP地址
char* inet_ntoa( struct in_addr in);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.4 TCP的连接

TCP协议通过三个报文段的完成来建立连接,建立的过程称为三次握手。
第一次握手:Client发送SYN包(syn=j)到Server并进入到SYN_SEND状态,等待Server确认SYN同步序列编号。
第二次握手:Server收到SYN包后必须确认Client的SYNACK=j+1,同时Server也发送一个SYN+ACK包,
此时Server进入SYN_RECV状态。
第三次握手:Client收到Server的SYN_ACK包后,向Server发送确认包ACK(ack=k+1),
此包发送完毕后Client和Server进入ESTABLISHED状态。完成三次握手。

一次完整的握手是 请求–应答–确认


在这里插入图片描述
当客户端调用connect时触发连接请求,向服务器发送了SYN包。这时connect进入阻塞状态。
服务器监听到连接请求后调用accept函数接受请求向客户端发送SYN+ACK,这时accept进入阻塞状态
客户端收到服务端的SYN+ACK包后,这时connect返回并向服务端发送SYN K进行确认,服务端收到SYN K 时accept返回。至此三次握手结束

2.5 TCP连接的断开

终止一个连接要经过四次握手(四次握手释放)


在这里插入图片描述
TCP连接是全双工的每个方向都必须单独进行关闭。当一方完成数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭而另一方执行被动关闭。
首先进行关闭的一方将执行主动关闭而另一方执行被动关闭。

  1. 客户端A发送一个FIN用来关闭客户A到服务器B的数据传送
  2. 服务器B收到这个FIN它发回一个ACK确认序号为收到的序号加1。和SYN一样一个FIN将占用一个序号。
  3. 服务器B关闭与客户端A的连接发送一个FIN给客户端A。
  4. 客户端A发回ACK报文确认并将确认序号设置为收到序号加1。

为什么建立连接协议是三次握手而关闭连接却是四次握手呢?
因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后它可以把ACK和SYNACK起应答作用而SYN起同步作用放在一个报文里来发送。但关闭连接时当收到对方的FIN报文通知时仅仅表示对方没有数据发送给你了但你所有的数据未必都全部发送给对方了你未必会马上会关闭SOCKET你可能还需要发送一些数据给对方之后再发送FIN报文给对方来表示你同意现在可以关闭连接了所以ACK报文和FIN报文多数情况下都是分开发送的。

为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
虽然双方都同意关闭连接了而且握手的4个报文也都协调和发送完毕按理可以直接回到CLOSED状态就好比从SYN_SEND状态到 ESTABLISH状态那样但是因为我们必须要假想网络是不可靠的你无法保证你最后发送的ACK报文会一定被对方收到因此对方处于 LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文而重发FIN报文所以这个TIME_WAIT状态的作用就是用来重发可能丢失的 ACK报文。

2.6 getaddrinfo

/* node:一个主机名域名或者地址串(IPv4点分十进制串或者IPv6的16进制串)
** service服务名可以是十进制的端口号可以是已定义服务名如ftp、http等
** hints可以是一个空指针也可以是一个指向某个addrinfo结构体的指针
** result本函数通过result指针参数返回一个指向addrinfo结构体链表的指针。
**
**返回值0——成功非0——出错
*/
int getaddrinfo(const char *node, const char *service,
                       const struct addrinfo *hints,
                       struct addrinfo **res);


/*
ai_flags:
    AI_PASSIVE套接字地址用于监听绑定
    AI_CANONNAME需要一个规范名而不是别名
    AI_V4MAPPED如果没有找到IPV6地址返回映射到IPV6格式的IPV4地址
    AI_ADDRCONFIG查询配置的地址类型IPV4或IPV6
    AI_NUMERICSERV以端口号返回服务
    AI_NUMERICHOST以数字格式返回主机地址
   gethostbyname函数仅支持IPV4

*/
struct addrinfo {
               int              ai_flags;
               int              ai_family;//AF_INET,AF_INET6或者AF_UNSPEC
               int              ai_socktype;//SOCK_STREAM or SOCK_DGRAM
               int              ai_protocol;//0
               size_t           ai_addrlen;
               struct sockaddr *ai_addr;
               char            *ai_canonname;
               struct addrinfo *ai_next;
           };
             
//name主机名或域名
struct hostent *gethostbyname(const char *name);
struct hostent {
               char  *h_name;            /* official name of host */
               char **h_aliases;         /* alias list */
               int    h_addrtype;        /* host address type */
               int    h_length;          /* length of address */
               char **h_addr_list;       /* list of addresses */
           }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

三、Socket接口函数

Socket编程的一般流程如下


在这里插入图片描述

3.1 socket函数

/*
* @brief 创建一个Socket
*
* @param domain 协议域又称为协议族family(AF_INET、AF_INET6、AF_LOCA),
* 		  协议族决定了socket的地址类型在通信中必须采用对应的地址如AF_INET决定了要用ipv4地址32位的与端口号16位的的组合
* @param type 指定socket类型(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等)
* @parm  protocol 指定协议( IPPROTO_TCP、IPPTOTO_UDP对应为CP传输协议、UDP传输协议)。
* 		 当protocol为0时会自动选择type类型对应的默认协议。
* @return 错误时返回-1
*/
int socket(int domain, int type, int protocol);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.2 bind函数

/*
* @brief 把一个地址族中的特定地址赋给socket
* 
* @param sockfd 即socket描述字通过socket函数创建得到唯一标识一个socket 
* @param addr 指针指向要绑定给sockfd的协议地址
* @param addleen 结构体sockaddr的长度
*
* @return 成功则返回0 ,失败返回-1
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

//IPV4的协议地址结构
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 */
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

3.3 listen函数

/*
* @brief 设置sockfd套接字为监听套接字
*
* @param sockfd 即为要监听的socket描述字
* @param backlog参数为相应socket可以排队的最大连接个数。
*
* @return 若成功则返回0,若出错则为-1
* 
* @note  socket函数创建的socket默认是一个主动类型的listen函数将socket变为被动类型的等待客户的连接请求。 
*/ 
int listen(int sockfd, int backlog);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.4 accept函数

/*
* @berif 接收客户端的请求建立连接套接字
*
* @param sockfd 是监听套接字用来监听一个端口
* @param addr用来接收客户端的协议地址可以设置为NULL
* @param len 表示接收的客户端的协议地址addr结构的大小的可设置为NULL
* 
* @return socket描述符,与客户端通信就靠这个
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

如果accept成功返回,则服务器与客户端已经正确连接了。服务器通过返回的套接字来完成与客户端的通信。
accept默认会阻塞进程直到有一个客户连接建立后返回返回的是一个新可用的连接套接字。
连接套接字accept函数返回的是连接套接字,代表与客户端已经建立连接

3.5 connect函数

/*
* @berif 建立与TCP服务器的连接
*
* @param sockfd 即为客户端的socket描述字
* @param addr 参数为服务器的socket地址
* @param addrlen 参数为socket地址的长度
* 
* @return 成功则返回0 ,失败返回-1
*/
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3.6 数据传输操作

/*
* @berif 负责从连接套接字fd中读取内容
*
* @param fd 参数是accept函数建立的连接套接字
* @param buf 参数是读取的内容存放的内存缓冲区
* @param count 参数是要读取的内容的大小
* 
* @return 当读成功时read返回实际所读的字节数如果返回的值是0表示已经读到文件的结束小于0表示出现了错误
*/
ssize_t read(int fd, void *buf, size_t count);

/*
* @berif 向连接套接字fd写入内容
*
* @param fd 参数是accept函数建立的连接套接字
* @param buf 参数表示要写入内容所在的内存缓冲区
* @param count 参数表示要写入的内容的大小
* 
* @return 当写成功时read返回实际所写的字节数,如果返回的值是0如果相应的errno被设定,说明有相应失败情况。
*   	  如果errno没有被设定,没有任何影响 小于0表示出现了错误
*/
ssize_t write(int fd, const void *buf, size_t count);

/*
* @berif 向连接套接字sockfd发送内容
*
* @param fd 参数表示发送到的连接套接字
* @param buf 参数表示要发送的内容所在的内存缓冲区
* @param len 参数表示要发送内容的长度
* @param flags 参数表示send的标识符一般为0
* 
* @return 成功返回实际发送的字节数出错返回-1
*/
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

/*
* @berif 从连接套接字sockfd接收内容
*
* @param fd 参数表示从哪个连接套接字接收内容
* @param buf 参数表示接收的内容存放的内存缓冲区
* @param len 参数表示要发送内容的长度
* @param flags 参数表示recv操作标识符一般为0
* 
* @return 成功返回实际发送的字节数出错返回-1
*/
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

/*
* @berif 向连接套接字sockfd发送信息
*
* @param fd 参数表示向哪个连接套接字发送信息
* @param msg 参数表示要发送的信息的内存缓冲区
* @param flags 参数表示sendmsg函数操作的标识,一般为0MSG_DONTWAIT表示非阻塞模式,MSG_OOB表示发送带外数据
* 
* @return 成功返回实际发送的字节数出错返回-1
*/
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

struct msghdr {
               void         *msg_name;       /* optional address */
               socklen_t     msg_namelen;    /* size of address */
               struct iovec *msg_iov;        /* scatter/gather array */
               size_t        msg_iovlen;     /* # elements in msg_iov */
               void         *msg_control;    /* ancillary data, see below */
               socklen_t     msg_controllen; /* ancillary data buffer len */
               int           msg_flags;      /* flags on received message */
           };

/*
* @berif 从连接套接字scokfd接收信息
*
* @param fd 参数表示从哪个连接套接字接收信息
* @param msg 参数表示接收的信息存放的内存缓冲区
* @param flags 参数表示recvmsg函数操作的标识符
* 
* @return 成功返回实际发送的字节数出错返回-1
*/
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

/*
* @berif 表示向连接套接字发送内容
*
* @param sockfd 参数表示向哪个连接套接字发送信息
* @param buf 参数表示发送的内容所在的内存缓冲区
* @param len 参数表示发送的信息的字节数
* @param flags 参数表示sendto函数的操作标识符
* @param dest_addr 参数表示发送到的地址的指针
* @param addrlen 参数表示发送到的地址的长度
* 
* @return 成功则返回实际传送出去的字符数,失败返回-1
*/
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

/*
* @berif 从连接套接字sockfd接收信息
*
* @param sockfd 参数表示从哪个连接套接字接收信息
* @param buf 参数表示接收的信息存放的内存缓冲区
* @param len 参数表示接收的实际字节数
* @param flags 参数表示recvfrom函数的操作标识符
* @param src_addr 参数表示接收的信息来自的主机协议地址所存放的内存缓冲区
* @param addrlen 参数表示接收信息的源主机协议地址的长度
* 
* @return 成功则返回实际接受的字符数,失败返回-1
*/
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
                     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109

3.7 close函数

/*
* @berif 关闭断开连接套接字
*
* @param fd 参数表示要断开的连接套接字
* 
* @return 关闭成功返回0,失败则返回-1.
*/
int close(int fd);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

四、程序示例

服务端server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
 
#define PORT  8888
#define LISTEN_QUEUE   10
#define BUFFER_SIZE 1024
 
int main()
{
    ///定义sockfd
    int listenfd = socket(AF_INET,SOCK_STREAM, 0);
    ///定义sockaddr_in
    struct sockaddr_in server_sockaddr;
    bzero(&server_sockaddr, sizeof(server_sockaddr));
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    ///bind成功返回0出错返回-1
    if(bind(listenfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr))==-1)
    {
        fprintf(stderr, "bind function failed.\n");
        exit(-1);
    }
    ///listen成功返回0出错返回-1
    if(listen(listenfd,LISTEN_QUEUE) == -1)
    {
        fprintf(stderr, "listen function failed.\n");
        exit(-1);
    }
    fprintf(stdout, "listening on %d\n", PORT);
    ///客户端套接字
    char recvbuf[BUFFER_SIZE];
    char sendbuf[BUFFER_SIZE];
    struct sockaddr_in client_addr;
    socklen_t length = sizeof(client_addr);
    bzero(&client_addr, sizeof(client_addr));
    ///成功返回非负描述字出错返回-1
    int connsockfd = accept(listenfd, (struct sockaddr*)&client_addr, &length);
    if(connsockfd<0)
    {
        fprintf(stderr, "connect function failed.\n");
        exit(-1);
    }
    while(1)
    {
        bzero(recvbuf, sizeof(recvbuf));
        bzero(sendbuf, sizeof(sendbuf));
        int len = recvfrom(connsockfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&client_addr, &len);
        if(strcmp(recvbuf,"exit\n")==0)
            break;
        if(strcmp(recvbuf,"q\n")==0)
            break;
        if(strcmp(recvbuf,"quit\n")==0)
            break;
        fprintf(stdout, "have a new client:%s\n", inet_ntoa(client_addr.sin_addr));
        fprintf(stdout, "message: %s\n", recvbuf);
        strcpy(sendbuf, recvbuf);
        send(connsockfd, sendbuf, len, 0);
        
    }
    close(connsockfd);
    close(listenfd);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

客户端client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
 
#define PORT  8888
#define BUFFER_SIZE 1024
 
int main()
{
    ///定义sockfd
    int clientsockfd = socket(AF_INET, SOCK_STREAM, 0);
    ///定义sockaddr_in
    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);  ///服务器端口
    servaddr.sin_addr.s_addr = inet_addr("192.168.0.200");  ///服务器ip
    ///连接服务器成功返回0错误返回-1
    if (connect(clientsockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    {
        fprintf(stderr, "connect function failed.\n");
        exit(-1);
    }
    char sendbuf[BUFFER_SIZE];
    char recvbuf[BUFFER_SIZE];
    bzero(sendbuf, sizeof(sendbuf));
    bzero(recvbuf, sizeof(recvbuf));
    while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
    {
        send(clientsockfd, sendbuf, strlen(sendbuf),0); ///发送
        if(strcmp(sendbuf,"exit\n")==0)
            break;
        if(strcmp(sendbuf,"q\n")==0)
            break;
        if(strcmp(sendbuf,"quit\n")==0)
            break;
        recv(clientsockfd, recvbuf, sizeof(recvbuf),0); ///接收
        fprintf(stdout, "%s\n", recvbuf);
bzero(sendbuf, sizeof(sendbuf));
        bzero(recvbuf, sizeof(recvbuf));
    }
    close(clientsockfd);
    return 0;
}



      recv(clientsockfd, recvbuf, sizeof(recvbuf),0); ///接收
        fprintf(stdout, "%s\n", recvbuf);
bzero(sendbuf, sizeof(sendbuf));
        bzero(recvbuf, sizeof(recvbuf));
    }
    close(clientsockfd);
    return 0;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/147309
推荐阅读
相关标签
  

闽ICP备14008679号