Socket(套接字)详解(与TCB)

一个TCB数据块包含了数据发送双方对应的socket信息以及拥有装载数据的缓冲区。在两个设备要建立连接发送数据之前,双方都必须要做一些准备工作,分配内存建立起TCB数据块就是连接建立前必须要做的准备工作。我们还需要了解的一点是TCP连接的建立方式,由于TCP协议建立在服务器–客户端的模式之上,因此对于两种不同角色的设备,他们发起连接的方式不一样。客户端发起连接的方式叫Active Open。也就是客户端需要主动向服务器发送消息,表达自己想建立数据连接的请求,通常而言客户端会向服务器发送一个SYNC数据包。服务器发起连接的方式叫Passive Open,通来说服务器不可能知道当前时刻有哪个设备想向它发起连接,因此它只能构建一个端口,然后监听该端口,等待客户端从该端口向它发起连接请求。在OPEN阶段无论是客户端还是服务器都需要准备好TCB数据结构,但由于服务器不知道要连接它的客户端信息,因此在构建TCB模块时会默认将客户端对应的socket数据初始化为0。

大多数操作系统使用系统调用的机制在应用程序和操作系统之间传递控制权。系统调用接口实际上就是应用进程的控制权和操作系统的控制权进行转换的一个接口。由于应用程序在使用系统调用之前要编写一些程序,特別是需要设置系统调用中的许多参数,因此这种系统调用接口又称为应用编程接口API。socket就是应用进程和运输层协议之间的一种接口。

套接字的通信原理简单示意图如下,左端通过文件描述符将数据写入发送端缓冲区,右端从接受端缓冲区接受数据,也可以是左端读数据,右端写数据。左右的缓冲区之间就是通过套接字连接。可以看出,socket在通信过程中一定是成对出现(接受端socket和发送端socket)。

每一条TCP连接唯一地被通信两端的两个端点(即两个套接字)所确定。即:

TCP连接::= { socket,, socket2} = {(IP1:port), (IP2: portz)}

socket 基础API(创建、命名。监听、接受连接、发起连接socket)

当格式化的数据(比如32 bit整型数和16 bit短整型数)在两台使用不同字节序的主机之间直接传递时,接收端必然错误地解释之。解决问题的方法是:发送端总是把要发送的数据转化成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。因此大端字节序也称为网络字节序。
 

 Socket函数

  • (1)socket函数:创建套接字,成功返回指向该套接字的文件描述符,失败返回-1。socket是文件,文件描述符就是指向他的索引,存储在内核中。

    int sockfd = socket(AF_INET,SOCK_STREAM,0);//创建监听套接字//int socket(int domain,int type,int protocol);
    if ( sockfd == -1 )//domain  参数告诉系统使用哪个底层协议族,type指定服务类型(流服务SOCK_STREAM TCP协议,传输层UDP 协议:SOCK_DGRAM)
    {                         //Socket 第三个参数是再选择一个具体的协议。基本设置为0  ,表示使用默认协议。
        exit(1);
    }

//创建监听套接字//int socket(int domain,int type,int protocol);
//domain 参数告诉系统使用哪个底层协议族;
type指定服务类型(流服务SOCK_STREAM TCP协议,传输层UDP 协议SOCK_DGRAM);
//Socket 第三个参数是再选择一个具体的协议。基本设置为0 ,表示使用默认协议。
#include <sys/socket.h>
int socket(int af, int type, int protocol);
//af 为地址族(Address Family)
//type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字,如TCP) 和 SOCK_DGRAM(数据报套接字/无连接的套接字,如UDP)。
//protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。

  • (2)bind函数绑定IP和端口号(struct sockaddr_in addr 初始化)当套接字被创建后,它的端口号和IP地址都是空的,因此应用进程要调用bind(绑定)来指明套接字的本地地址(本地端口号和本地IP地址)。在服务器端调用bind时就是把熟知端口号和本地IP地址填写到已创建的套接字中。这就叫做把本地地址绑定到套接字。在客户端也可以不调用bind,这时由操作系统内核自动分配一个动态端口号(通信结束后由系统收回)。 bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen参数指出该socket地址的长度。bind成功时返回0,失败则返回-1并设置errno。其中两种常见的errno是EACCES和EADDRINUSE。
#include<sys/socket.h>
#include<sys/type.h>
int bind(int sockfd, const struct sockaddr * my_addr, socklen_t addrlen);
//sockfd 表示已经建立的socket编号(描述符)。
//my_addr 是一个指向sockaddr结构体类型的指针。
//addrlen表示my_addr结构的长度,可以用sizeof操作符获得。
  • (3)listen函数:服务器在调用bind后,还必须调用listen(收听)把套接字设置为被动方式,以便随时接受客户的服务请求。UDP服务器由于只提供无连接服务,不使用listen系统调用。指定同时支持的最大连接数.成功返回0,失败返回-1。
#include <sys/socket.h>
int listen( int sockfd, int backlog);
//sockfd参数是执行过listen系统调用的监听socket(文件描述符)。
  • (4)accept函数:接受连接请求。成功返回一个新的socket文件描述符,用来和客户端通信,失败返回-1。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//sockfd表示文件描述符。
//addr为传出参数,返回链接客户端地址信息,含IP地址和端口号。
//addrlen为传入传出参数,传入sezeof(addr)的大小,函数返回时返回真正接受到地址结构体的大小。
  • (5)connect函数:建立与指定socket的连接,成功返回0,失败返回-1
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//sockfd表示socket文件描述符。
//addr为传入参数,指定服务器端地址信息,含IP地址和端口号
//addrlen为传入参数,传入sezeof(addr)的大小

Socket模型创建流程图

对于客户端来说不需要调用bind函数,因为没有调用bind函数,操作系统会自动分配一个IP和端口号,但是服务器端不能使用随机分配的。建立连接后,客户端把要传递的数据发送到缓冲池,服务端将数据读到自己的缓冲池中,当缓冲池数据到一定程度(可以设置)就写到本地存储中。

下面是客户端和服务器的详细代码。我们称发起连接的为客户端,接收方为服务器

//服务端代码
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <ctype.h>
#include <arpa/inet.h>

#define SERV_PORT 6666 //这里需要定义大点的端口号防止和系统已使用的冲突
int main(void){
    int lfd, cfd;
    struct sockaddr_in serv_addr, clie_addr;
    socklen_t clie_addr_len;
    char buf[BUFSIZ];
    int n, i;

    lfd = socket(AF_INET, SOCK_STREAM, 0);

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);//主机字节序转网络字节序
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY是数字类型的IP
    bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    listen(lfd, 128);//128为默认的上限值

    clie_addr_len = sizeof(clie_addr);
    cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
    while(1){
        n = read(cfd, buf, sizeof(buf));
        for (i = 0; i < n; i++){
            buf[i] = toupper(buf[i]);
          }
         write(cfd, buf, n);
    }

    close(lfd);
    close(cfd);

    return 0;
}
//客户端
#include <sys/socket.h>
#include <stdlib.h>
#include <ctype.h>
#include <arpa/inet.h>

#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666 //这里需要定义大点的端口号防止和系统已使用的冲突
int main(void){
    int cfd;
    struct sockaddr_in serv_addr;
    socklen_t serv_addr_len;
    char buf[BUFSIZ];
    int n;

    cfd = socket(AF_INET, SOCK_STREAM, 0);

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);//主机字节序转网络字节序
    inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);

    connect(cfd, (struct socketaddr *)&serv_addr, sizeof(serv_addr));

    while(1){
    fgets(buf, sizeof(buf),stdin);
    write(cfd,buf,strlen(buf));

    n = read(cfd,buf,sizeof(buf));
    write(STDOUT_FILENO, buf, n);
    }
    return 0;
}

在server端,socket函数只是创建了套接字,并没有完成两个进程间通信,而是accept函数完成这件事,它返回一个套接字sfd,是一个文件描述符索引,read读sfd指向的缓冲区中的数据,write往sfd指向的缓冲区中写数据。在client端,通过fgets从标准输入缓冲区中读数据,然后通过write写到cfd指向的缓冲区中,然后通过IP+Port就能找到服务器端,服务器端的read就能读取到数据。详细描述为以下10个步骤:

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值