Socket编程的简单使用与介绍

套接字地址结构

进行套接字编程需要指定套接字的地址作为参数。不同的协议族有不同的地址结构定义方式。 这些地址结构通常以sockaddr_开头,每一个协议族有一个唯一的后缀。

通用套接字数据结构

struct sockaddr { 			//套接字地址结构
 sa_family_t sa_family; 	//协议族  unsigned short类型 长度为16个字节
 char sa_data[14];  		//协议族数据
}

实际使用的套接字数据结构

因为通用套接字在实际使用中并不方便,比如在以太网中,一般采用struct sockaddr_in进行设置

struct sockaddr_in { 			//以太网套接字地址结构 
 u8 sin_len; 					//结构struct sockaddr_in的长度
 u8 sin_family; 				//通常为AF_INET
 u16 sin_port; 					//16位的端口号,网络字节序
 struct in_addr sin_addr; 		//IP地址32位 
 char sin_zero[8]; 				//未用到 
};

socket数据如何在内核中接收和发送

socket数据在内核中的流程主要包括初始化、销毁、接收和发送网络数据。其过程涉及网卡驱动、网络协议栈和应用层的接口函数。

套接字参数中有部分参数是需要用户传入的,这些参数用来与Linux内核进行通信,例如指向地址结构的指针。

向内核传入数据的函数有send(),bind()等,从内核中得到数据的函数有accept(),recv()等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XUrlB90N-1649165402418)(https://gitee.com/znxcode/drawingbed/raw/master/img/image-20220320160123829.png)]

/*创建套接字并进行bind*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>

#define MYPORT 3490

int main() {
    int sockfd;                         //套接字文件描述符变量
    struct sockaddr_in my_addr;         //以太网套接字地址结构
    sockfd = socket(AF_INET, SOCK_STREAM,0); //建立套接字文件描述符
    //在建立套接字文件描述符后,需要对套接字进行端口和地址的绑定,才能进行数据的接收和发送操作
    if (sockfd == -1) { /*检查是否正常初始化 socket*/
        perror("socket");
        exit(EXIT_FAILURE);
        }
    my_addr.sin_family = AF_INET;       //协议族
    my_addr.sin_port = htons(MYPORT);       //端口地址
    my_addr.sin_addr.s_addr = inet_addr("192.168.232.138"); //将IP地址转化为网络字节序
    bzero(&(my_addr.sin_zero), 8);          //将my_addr.sin_zero置0
    if (bind(sockfd, (struct sockaddr *)&my_addr,
             sizeof(struct sockaddr)) == -1) { /*判断是否bind成功*/
        perror("bind");
        exit(EXIT_FAILURE);
        }
    close(sockfd);          //关闭套接字
    return 0;
}

listen():

该函数是用来初始化服务器可连接队列的。因为服务器在处理多个客户端请求时,并不是同时处理,而是将剩余的请求放在请求队列中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6fpBr3mU-1649165402419)(https://gitee.com/znxcode/drawingbed/raw/master/img/image-20220320212013545.png)]

上表为listen()的errno值及其含义

listen函数仅对SOCK_STREAM和SOCK_SEQPACKET的协议有效

    if  ((listen(sockfd, 5)) == -1) {           /*进行侦听绑定*/
        perror("listen");
        exit(EXIT_FAILURE);
        }

以上代码为侦听队列的绑定

accept():

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

通过accept函数可以得到成功连接客户端的IP地址、端口和协议族等信息,这个信息是通过参数addr获得的。当accept()返回的时候,会将客户端的信息存储在参数addr中。需要注意的是,addrlen参数是一个指针而不是结构,accept()将这个指针传给TCP/IP协议栈。

accept()函数的返回值是新连接的客户端套接字文件描述符。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>

#define MYPORT 3490

int main() {
    int sockfd;                         //套接字文件描述符变量
    int client_fd;                      //客户端套接字文件描述变量
    struct sockaddr_in my_addr;         //以太网套接字地址结构(本地地址信息)
    struct sockaddr_in client_addr;     //客户端连接的地址信息
    int addr_length;                    //用于保存网络地址长度
    sockfd = socket(AF_INET, SOCK_STREAM,0); //建立套接字文件描述符
    //在建立套接字文件描述符后,需要对套接字进行端口和地址的绑定,才能进行数据的接收和发送操作
    if (sockfd == -1) { /*检查是否正常初始化 socket*/
        perror("socket");
        exit(EXIT_FAILURE);
    }
    my_addr.sin_family = AF_INET;       //协议族
    my_addr.sin_port = htons(MYPORT);       //端口地址
    my_addr.sin_addr.s_addr = INADDR_ANY;   //自动IP地址获得
    bzero(&(my_addr.sin_zero), 8);          //将my_addr.sin_zero置0
    if (bind(sockfd, (struct sockaddr *)&my_addr,
             sizeof(struct sockaddr)) == -1) {           /*判断是否bind成功*/
        perror("bind");
        exit(EXIT_FAILURE);
    }
    if  ((listen(sockfd, 5)) == -1) {           /*进行监听绑定*/
        perror("listen");
        exit(EXIT_FAILURE);
        }
    return 0;
    addr_length = sizeof(struct sockaddr_in);       //获取地址长度
    client_fd = accept(sockfd, & client_addr, & addr_length);  //等待客户端连接
    if(client_fd == -1){ /*accept 出错 */
        perror("accept");
        exit(EXIT_FAILURE);
        }
    close(client_fd);
    close(sockfd);          //关闭套接字
}

connect():

客户端在建立套接字之后,不需要进行地址绑定,就可以直接连接服务器。

int connect(int sockfd, struct sockaddr *, int addrlen);

TCP通信例子

server.c

int main(int argc, char *argv[])
{
    int ss,sc;      //ss为服务器的描述符,sc为服务器的描述符
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int err;
    pid_t pid;      //定义进程ID号
    ss = socket(AF_INET, SOCK_STREAM, 0);  //建立套接字描述符
    if(ss < 0){
        printf("socket error\n");
        return -1;
    }
    bzero(&server_addr, sizeof(server_addr));       //置0
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);
    err = bind(ss, (struct sockaddr*)&server_addr, sizeof(server_addr));  /*将套接字描述符和
 * 套接字结构bind*/
    if(err < 0){
        printf("bind error\n");
        return -1;
    }
    err = listen(ss, BACKLOG);     //创建监听且数量为2
    if(err < 0){
        printf("listen error\n");
        return -1;
    }
    for(;;) {           //开始循环等待客户端连接
        int addrlen = sizeof(struct sockaddr);
        sc = accept(ss, (struct sockaddr*)&client_addr, &addrlen);  //接收客户端连接请求
        if(sc < 0){
            continue;           //请求失败退出该次循环
        }
        pid = fork();           //分叉进程
        if( pid == 0 ){         //判断是否仍然是在等待客户端连接的进程
            close(ss);
            process_conn_server(sc);
        }else{                  //
            close(sc);
        }
    }
}

client.c

int main(int argc, char *argv[])
{
    int s;
    struct sockaddr_in server_addr;
    int err;

    s = socket(AF_INET, SOCK_STREAM, 0);
    if(s < 0){
        printf("socket error\n");
        return -1;
    }
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    inet_pton(AF_INET, argv[1], &server_addr.sin_addr); //将用户传入的字符串类型的IP地址转换为整型

    connect(s, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));  //连接服务器
    process_conn_client(s);         //客户端处理过程
    close(s);
}

proccess.c

void process_conn_server(int s)
{
    ssize_t size = 0;
    char buffer[1024];      //数据缓冲区
    for(;;){
        size = read(s, buffer, 1024); //从套接字中读取放到缓冲区的buffer
        if(size == 0){
            return;
        }
        sprintf(buffer, "%d bytes altogether\n", size);
        write(s, buffer, strlen(buffer));  //将返回数据发送给客户端
    }
}
void process_conn_client(int s)
{
    ssize_t size = 0;
    char buffer[1024];
    for(;;){
        size = read(0, buffer, 1024);
        if(size > 0){
            write(s, buffer, size);
            size = read(s, buffer, 1024);
            //sprintf(buffer, "%d bytes altogether\n", size);
            write(1, buffer, size);
        }
    }
}

套接字选项

在进行网络编程的时候,经常需要查看或者设置套接字的某些特性。例如设置地址复用、读写数据的超时时间、对读缓冲区的大小进行调整等操作。获得套接字选项设置情况的函数是getsockopt(),设置套接字选项的函数是setsockopt()

#include <sys/types.h> 
#include <sys/socket.h> 
int getsockopt(int s, int level, int optname, void *optval, socklen_t 
*optlen);   //获得套接字选项设置情况
int setsockopt(int s, int level, int optname, const void *optval, socklen_t 
optlen);	//设置套接字选项
  • s:套接字描述符
  • level:选项所在协议层
  • optname:选项名
  • optval:操作的内存缓存区
  • oplen:上面参数的长度
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值