【网络编程基础】第二节 Socket程序演示

学习地址:C语言中文网 - Linux Socket程序演示

C/S Socket demo 地址: github



Create Socket

int socket (int af, int type, int protocol)

  • af(address family):地址族,IP地址类型,常用AF_INET 和AF_INET6 分别来表示 IPv4 和 IPv6。
  • type :数据传输类型,常用SOCK_STREAM 和 SOCK_DGRAM ,可以看第一节中对type的介绍。
  • protocol :传输协议,常用IPPROTO_TCP 和 IPPROTO_UDP 分别表示TCP 和 UDP 传输协议。

演示代码:

int socketTCP = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int socketUDP = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
//!> 因为上述两种情况都是唯一的,所以可以参数 Protocol 可以选择 IPPROTO_IP(0),所以可以直接这样写:
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);


Service端 - bind()

int bind(int sock, struck sockaddr * addr, socklen_t addrlen); // like unix

  • sock :socket 文件描述符。
  • addr :sockaddr 结构体变量的指针。
  • addrlen :addr 变量的大小, 可由sizeof() 计算得出。

演示代码:

    int serv_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr)); //*> 结构体初始化为0
    serv_addr.sin_family = AF_INET;           //*> 使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //*> 服务器具体ip地址
    serv_addr.sin_port = htons(1234);         //*> host自序转换net字序
    
    bind(serv_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

bind() 第二个参数是通过struct sockadd_in 结构体类型强制转化为 sockaddr类型,为什么这样做?首先介绍sockaddr_in 结构体,

sockaddr_in 结构体

    /*
     * Socket address, internet style.
     */
    struct sockaddr_in {
        __uint8_t	sin_len;       //*> 结构体大小
        sa_family_t	sin_family;    //*> af(address family)
        in_port_t	sin_port;      //*> 16位的端口号
        struct	in_addr sin_addr;  //*> 32位的IP地址
        char		sin_zero[8];   //*> 不使用,用0填充
    };
  • sin_family 和 socket() 的第一个参数一致。
  • sin_port 的类型是__uint16_t ,长度为2个字节,理论取值范围 0~65536,但0 ~ 1023 的端口一般由OS 分给特定的服务程序,例如Web 服务端口为 80,FTP 服务的端口为 21,所以我们的程序要在1024~65536 之间分配端口号。然后通过htons() 转换!可以看我的enhance博客。
  • sin_addr 是struct in_addr 结构体类型变量。

struct in_addr 结构体

/*
 * Internet address (a structure for historical reasons)
 */
struct in_addr {
	in_addr_t s_addr;  //*> __uint32_t 单位, 32位的IP地址
};

为什么在bind() 第二个参数不直接用sockaddr_in 而使用 sockaddr 呢?

sockaddr 结构体的定义如下:

/*
 * [XSI] Structure used by kernel to store most addresses.
 */
struct sockaddr {
	__uint8_t	sa_len;		/* total length */
	sa_family_t	sa_family;	/* [XSI] address family */
	char		sa_data[14];	/* [XSI] addr value (actually larger)  ip:port 组合*/
};

首先sockaddr_in 和 sockaddr 的长度相同,都是18个字节数(旧版本都是16字节数,因为新版本都增加了__unit8_t sa_len 类型)
其次sockaddr 中 sa_data是IP地址和端口号的组合,例如“127.0.0.1:80”,但是,没有相关函数将这个字符串转换为需要的sa_data形式。
所以,通过sockaddr_in 强制转化代替 sockaddr,因为长度相同,转化后不会丢失字节,也没有多余的字节。
相同道理:IPv6:struct sockaddr_in6 ,这恰恰也说明了这种强转替换的好处,因为不同地址类型就会定义不同的结构体。这样方便给bind() 参数赋值。



Client端 - connect()

int connect(int sock, struct sockaddr * serv_addr, socklen_t addrlen); // like unix

参数说明同 bind() 。

演示代码:

- (void)client
{
    int serv_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(1234);
    serv_addr.sin_addr.s_addr = inet_addr("(服务器IP确切IP地址)");
    
    connect(serv_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
}


Service端 - listen()

int listen(int sock, int backlog); //*> like unix

  • sock :需要进入监听状态的套接字。
  • backlog :请求队列的最大长度。

Service端使用bind() 绑定套接字之后,还需要使用listen() 函数让套接字进入被监听状态,再调用 accept() 函数,就可以随时响应Client 的请求了。

listen() 所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收客户端请求时,套接字才会被“唤醒”来响应请求。

请求队列 - backlog####

当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没有办法处理的,只能把请求放进缓冲区,待当前请求处理完毕后,再从缓冲区读取出来处理。如果不断有新的请求进来,这些请求就会按照先后顺序在缓冲区排队,知道缓冲区满。这个缓冲区,就成为请求队列(Request Queue)。

缓冲区的长度(能存放多少个Client请求)可以通过listen() 函数的 backlog 参数确定,但毕竟为多少并没有标准,可以根据你的需要来定,并放量小的话可以是10或者20。

backlog 可以设置为SOMAXCONN,就由系统决定请求队列长度,这个值一般比较大,可能几百,或者更多。

当请求队列满时,就不会接收新的请求,对于like unxi,客户端就会收到ECONNREFUSED错误。

注意:listen() 只是让套接字处于监听状态,并没有接收请求。接收请求需要使用 accept() 函数。

演示代码:

    int serv_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr)); //*> 结构体初始化为0
    serv_addr.sin_family = AF_INET;           //*> 使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //*> 服务器具体ip地址
    serv_addr.sin_port = htons(1234);         //*> host自序转换net字序
    
    bind(serv_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    listen(serv_socket, 20); //*> 设定Service 端进入监听状态。



Service端 - accept()

int accept(int sock, struct socket * addr, socket_t * addrlen); //*> like unix

accept() 参数与 listen() 和 connect() 是相同的;

  • sock : Service端套接字。
  • addr :socket_in 结构体变量。
  • addlen :参数addr 的长度,可由 sizeof() 求得。

accept() 返回一个新的套接字来和Client端通信,addr 保存了Client 端的IP+Port,而sock 是Service 套接字,大家注意区分。后面和Client 端通信时,要使用accept() 新生成的套接字,而不是旧的服务端套接字。

accept() 会阻塞程序执行(后面的代码不能被执行),直到有新的请求到来。

    int serv_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr)); //*> 结构体初始化为0
    serv_addr.sin_family = AF_INET;           //*> 使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //*> 服务器具体ip地址
    serv_addr.sin_port = htons(1234);         //*> host自序转换net字序
    
    bind(serv_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    listen(serv_socket, 20); //*> 设定Service 端进入监听状态。
    
    // 堵塞程序执行,接收客户端请求
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addrlen = sizeof(clnt_addr);
    accept(serv_socket, (struct sockaddr *)&clnt_addr, &clnt_addrlen);


Scoket 的 write() 和 read()

Unix 不区分套接字文件和普通文件,使用write() 可以向套接字中写入数据,使用 read() 可以从套接字中读取数据。

两台计算机之间的通信相当于两个套接字之间的通信,在服务器端用 write() 想套接字写入数据,客户端就能收到,然后再使用 read() 从套接字中读取出来,就完成了一次通信。

write():

ssize_t write(int fd, const void *buf, size_t nbytes); // like unix

  • fd :写入文件的描述符。
  • buf :写入数据的缓冲区地址。
  • nbytes :写入数据的字节数。

write() 函数会将缓冲区 buf 中的nbytes 个字节写入文件 fd,成功函数返回写入的字节数,失败则返回 -1 。

read():

ssize_t read(int fd, void * buf , size_t nbytes); // like unix

  • fd :写入文件的描述符。
  • buf :写入数据的缓冲区地址。
  • nbytes :写入数据的字节数。

read() 函数会从 fd 文件中读取 nbytes 个字节并保存到缓冲区 buf,成功函数返回读取的字节数(但遇到文件结尾则返回 0),失败则返回 -1。



posted @ 2016-05-19 14:26  lvable  阅读(363)  评论(0)    收藏  举报