socket通信

socket编程

2018-04-18  本文已影响178人  wenmingxing

一切皆socket!本文介绍socket基础,socket的基本操作,并对socket中的TCP过程说明,还有一个例子用于实践。

I、socket的基本操作

socket是基于UNIX哲学中的“一切皆文件”的特例,采用“open-write/read-close”模式实现,我们一般说的socket编程,都是指的TCP编程。

1.1 socket()函数
int socket(int domain, int type, int protocol);  

1、socket()函数用于对应文件的打开操作,创建一个socket描述符,唯一标识所创建的socket。

2、socket()函数的三个参数分别为:
· domain:标识协议域,又称为协议族(family)。常用的协议族有AF_INET,AF_INET6, AF_LOCAL等,这个参数决定了socket的地址类型,如AF_INET为我们最常用的协议族,其是采用ipv4地址(32bits)与端口号(16bits)的组合;
· type:指定socket类型,最常用的就是SOCK_STREAM,这是TCP中以流的方式传输类型;
· protocol:指定协议,常用的有IPPROTO_TCP,IPPROTO_UDP,分别对应TCP传输协议、UDP传输协议,当protocol为0时,会自动选择类型type类型对应的默认协议。

1.2 bind()函数

1、bind()函数用于把一个地址族中的特定地址赋给socket。实现socket描述符与地址的绑定:

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

2、三个参数:
· sockfd:为socket()函数得到的socket描述符;
· addr:一个const struct sockaddr*指针,指向要绑定的sockfd的协议地址。下面是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;
};
struct in_addr {
    uint32_t    s_addr;    // address in network byte order
};

· addrlen: 对应的地址长度。

通常服务器在启动的时候会绑定一个众所周知的地址(IP地址+端口号),用于提供服务,客户端可以通过它来连接服务器;而客户端就不用指定,由系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不用调用,而是在connect()时由系统随机生成一个。

1.3 listen(), connect()函数

作为服务器,在调用bind()完成绑定之后,就会调用listen()来监听这个已经绑定好地址和端口的socket,将socket()创建的主动socket转化为监听socket

int listen(int sockfd, int backlog);

之后,客户端可以调用connect()发出连接请求,服务器就可以接受这个请求:

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

connect()的第一个参数为客户端的socket,第二个参数为服务器的地址,第三个参数为服务器地址长度。

1.4 accept()函数

服务器在返回listen()函数之后,所的到的监听socket,在收到一个connect()时会调用accept()函数接受connect请求:

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

第一个参数为服务器socket, 第二个参数用于返回客户端的协议地址,第三个参数为地址长度。如果accept()成功,则返回值为内核生成的一个已连接socket,用以处理这个连接。

一个服务器通常仅仅只创建一个监听socket,在服务器的生存周期一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket,当服务器完成这个服务之后,相应的已连接socket就被关闭。

1.5 read(), write()等函数

客户端与服务器建立好连接之后。可以调用网络I/O进行读写操作了,网络I/O操作由下面几组:
· read()/write()
· recv()/send()
· readv()/writev()
· recvmsg()/sendmsg()
· recvfrom()/sendto()

推荐使用recvmsg()/sendmsg()函数,这是最通用的I/O函数,实际上上面的其他函数都可以替换成这两个函数。它们的声明如下:

#include<unistd.h>

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
#include<sys/types.h>
#include<sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);  

ssize_t sento(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                            struct sockaddr *src_addr, socklen_t *addrlen);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

read()函数是负责从fd中读取内容到buf中,返回所读到的字节数。
write()函数将buf中的字节内容拷贝到fd上,返回成功拷贝的字节数。

1.6 close()函数

服务器可以使用close()函数关闭响应的socket:

#include<unistd.h>
int close(int fd);

close是将对应的socket引用计数-1, 只有当引用计数为0的时候,才会触发客户端向服务器发送终止连接请求。

II、socket中TCP三次握手与四次挥手

关于三次握手与四次挥手已经在wenmingxing TCP三次握手与四次挥手中详细说明,下面只介绍各个函数在每个阶段所起的作用。

2.1 三次握手

图1展示了TCP三次握手的过程,以及编程函数所出现的阶段:

图1、socket中的三次握手

1、客户端调用connect()触发第一次握手,此时connect()阻塞,直到收到服务器返回ACK。
2、服务器收到第一次握手SYN包之后,调用accept()并阻塞,直到收到第三次握手的ACK,accept()返回。三次握手完成,连接建立。

2.2 四次挥手

图2展示了TCP四次挥手的过程,以及编程函数所出现的阶段:

图2、socket中的四次挥手

III、一个服务器与客户端实例

3.1 服务器端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>

#define MAXLINE 4096

int main() {
    int listenfd, connfd;
    struct sockaddr_in servaddr;
    char buff[MAXLINE];
    int n;

    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("socket error");
        exit(0);
    }

    memset(&servaddr, 0, sizeof(servaddr)); //将servaddr全部初始为0,完成初始化
    //为servaddr赋值
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);   //INADDR_ANY为0.0.0.0,设置IP 地址的时候使用htonl
    servaddr.sin_port = htons(6666);    //设置端口为6666,使用htons

    if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
        printf("bind error");
        exit(0);
    }

    if (listen(listenfd, 10) == -1) {
        printf("listen error");
        exit(0);
    }

    printf("===============waiting for client's request=================\n");

    while (1) {
        if ((connfd == accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1) {
            printf("accept error");
            continue;
        }    
        n = recv(connfd, buff, MAXLINE, 0); //相当于将connfd中接收到的东西copy到buff中
        buff[n] = '\0';
        printf("recv msg from client: %s\n", buff);
        close(connfd);  //关闭这个已连接的socket
    }

    close(listenfd);    //最后关闭唯一的监听socket
}
3.2 客户端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>

#define MAXLINE 4096

int main(int argc, char** argv) {
    int sockfd, n;
    char recvline[MAXLINE], sendline[MAXLINE];
    struct sockaddr_in servaddr;

    if (argc != 2) {
        printf("paraments error");
        exit(0);
    }

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("socket error");
        exit(0);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(6666);
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {
        printf("inet_pton error");
        exit(0);
    }

    printf("==========send msg to server=============\n");
    //fgets(sendline, MAXLINE, stdin);
    scanf("%s", sendline);

    if (send(sockfd, sendline, strlen(sendline), 0) < 0) {
        printf("send error");
        exit(0);
    }

    close(sockfd);
    exit(0);

}

【参考】
[1] Linux Socket编程
[2] 《深入理解计算机系统》

欢迎转载,转载请注明出处wenmingxing socket编程

上一篇下一篇

猜你喜欢

热点阅读