socket建立连接

通过pipe,fifo等可以直接同一台机器上进行进程间通信,而通过网络相连的机器之间进行进程间通信要用到套接字接口,实际上套接字可以用在同一台机器上,也可以用在不同的机器上;也就是既可以内部通信,又可以相互之间通信

套接字是通信端点的抽象,和文件描述符一样,访问套接字也需要套接字描述符,处理文件描述符的read,write都可以处理套接字描述符

1:调用socket函数就会创建一个socket描述符,它唯一标识了一个socket,跟文件描述符一样,后面的操作会用到它,用它作参数来进行一些读写

#include <sys/socket.h>

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

函数如果创建成功,则返回套接字描述符;如果失败则返回-1

参数domain表示通信的特性,比如地址格式;在下面的表格里,每个通信域都有独自的地址格式,表示不同域以AF_开头,地址族

域                      描述
AF_INET         IPV4因特网域
AF_INET6        IPV6因特网域
AF_UNIX         UNIX域
AF_UNSPEC       未指定

参数type表示套接字的类型,进一步确定通信特性,下面表格定义了套接字类型

类型                                    描述
SOCK_DGRAM                      长度固定,无连接的不可靠报文传递
SOCK_RAW                        IP协议的数据包接口
SOCK_SEQPACKET                  长度固定,有序,可靠的面向连接报文传递
SOCK_STREAM                     有序,可靠,双向的面向连接字节流

参数protocol通常是0,表示给定的域和套接字类型选择默认协议;如果对同一域和套接字类型支持多个协议时,可以使用protocol参数选择一个特定协议;在AF_INET域中套接字类型SOCK_STREAM默认协议是TCP,在AF_INET域中套接字类型SOCK_DGRAM的默认协议是UDP

2:将地址绑定到一个套接字,对于服务器给一个接收客户端请求的套接字绑定一个地址

#include <sys/socket.h>

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

函数绑定成功返回0,如果失败了就返回-1

参数sockfd也就是socket描述符,通过socket()创建的

参数addr是一个const struct sockaddr *的指针,指向要绑定给sockfd的协议地址,这里的地址必须和创建套接字的时候的地址族格式匹配,IPV4,IPV6和UNIX结构体都不同

参数len表示地址长度

通常服务器在启动的时候,绑定一个大家都知道的地址(ip addr + port)用于提供服务,客户端就可以通过它来连接服务器;但是客户端不需要指定,有哪位已经有系统分配的地址族;所以通常服务器在listen之前调用bind(),而客户端步调用,而在connect()时由系统随机生成一个

网络字节序

由于TCP/IP首部中所有二进制整数在网络中传输的次序为0~7bit,8~15bit,16~23bit,24~31bit顺序传输,因此也称为网络字节序,显然这里只有大端这一种字节序;但是主机字节序却有大端和小端两种字节序,所以在将一个地址绑定到socket的时候,先将主机字节序转换成网络字节序

3:服务器端在调用socket()和bind()之后就会调用listen()来监听这个socket;如果客户端调用connect()发出连接请求,服务器就受到了

#include <sys/socket.h>

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);

参数backlog提供了一个提示,用于表示该进程索要入队的连接请求数量

对于connect函数,假如sockfd没有绑定一个地址,connect会给调用者绑定一个默认地址

4:TCP服务端调用了socket(),bind(),listen()后就在监听指定的socket地址了,TCP客户端调用了socket(),connect()后向TCP服务器发送一个连接请求,服务器监听了这个请求之后,会调用accept()函数来接收请求,这样请求就建立好了,之后就可以进行文件读写等操作了

#include <sys/socket.h>

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

函数如果成功返回socket描述符,假如失败返回-1

成功返回的socket是已连接的socket描述符,而不是第一个参数传进去的服务器最开始创建的;一个服务器只会创建一个监听socket描述符,内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述符,当服务器完成了对客户端的服务,对应已经连接的socket描述符也关闭

下面是一个简单的建立客户端和服务器之间建立连接的例子:

client:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 9999
#define IPADDR "127.0.0.1"

int main(int argc, char **argv){
    int client_sockfd;
    struct sockaddr_in server_address;

    if ((client_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
        printf("create socket failed\n");
        exit(1);
    }

    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(PORT);
    if (inet_pton(AF_INET, IPADDR, &server_address.sin_addr) == -1){
        printf("inet_pton failed!\n");
        exit(1);
    }

    if (connect(client_sockfd, (struct sockaddr *)&server_address, sizeof(server_address)) == -1){
        printf("connect failed!\n");
        exit(1);
    }
    close(client_sockfd);
    return 0;
}

server:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 9999

int main(){
    int server_sockfd, connect_sockfd;
    struct sockaddr_in server_address;

    if ((server_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
        printf("Create socket failed!\n");
        exit(1);
    }
    memset(&server_address, 0, sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(PORT);

    if (bind(server_sockfd, (struct sockaddr *)&server_address, sizeof(server_address)) == -1){
        printf("bind failed!\n");
        exit(1);
    }

    if (listen(server_sockfd, 10) == -1){
        printf("listen socket failed!\n");
        exit(1);
    }
    if ((connect_sockfd = accept(server_sockfd, (struct sockaddr *)NULL, NULL)) == -1){
        printf("connect failed!\n");
        exit(1);
    }
    printf("Connect sucessfully!\n");
    close(connect_sockfd);
    close(server_sockfd);
    return 0;
}

编译,运行:

服务端:
lihui@2015 /cygdrive/d/work
$ cc -o client client.c

lihui@2015 /cygdrive/d/work
$ cc -o server server.c

lihui@2015 /cygdrive/d/work
$ ./server.exe
光标

客户端:
lihui@2015 /cygdrive/d/work
$ ./client.exe

服务端:
lihui@2015 /cygdrive/d/work
$ ./server.exe
Connect sucessfully!

这样,一个简单的连接就建立起来了

发表回复