一.概念介绍

1.socket 是什么?

  socket(套接字)本质上是一个抽象的概念,它是一组用于网络通信的 API,提供了一种统一的接口,使得应用程序可以通过网络进行通信。在不同的操作系统中,socket 的实现方式可能不同,但它们都遵循相同的规范和协议,可以实现跨平台的网络通信。

2.socket实现通信的原理是基于网络协议栈。

  协议栈是一个由多个层次协议组成的网络协议体系结构,它负责对数据进行封装和解封装,并确保数据能够在网络上正确传输。

  当应用程序通过 socket 发送数据时,操作系统会将数据传递给协议栈的上层协议,该协议会对数据进行封装并添加一些必要的信息,例如目标 IP 地址和端口号等。然后将封装后的数据传递给下一层协议,直到数据最终被封装成一个网络包并通过网络发送到目标主机。

  当目标主机收到网络包后,协议栈会对数据进行解封装,并将数据传递给操作系统中的套接字。如果该套接字是一个监听套接字,操作系统会创建一个新的套接字来处理连接请求,并将新的套接字加入到协议栈中。如果该套接字是一个已连接套接字,操作系统会将数据传递给应用程序处理。

3.socket 是一个系统接口函数,由操作系统提供,用于实现网络编程的功能。通过 socket 函数,应用程序可以创建套接字、绑定地址、监听连接、发送和接收数据等操作,从而实现网络通信。

二.api

1.Linux下socket编程需要用到的相关函数:

API参数介绍:

<sys/socket.h>

#include <sys/types.h>          /* See NOTES */

#include <sys/socket.h>

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

AF_UNIX, AF_LOCAL   Local communication              unix(7)

AF_INET             IPv4 Internet protocols          ip(7)

AF_INET6            IPv6 Internet protocols          ipv6(7)

AF_IPX              IPX - Novell protocols

AF_NETLINK          Kernel user interface device     netlink(7)

AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)

AF_AX25             Amateur radio AX.25 protocol

AF_ATMPVC           Access to raw ATM PVCs

AF_APPLETALK        AppleTalk                        ddp(7)

AF_PACKET           Low level packet interface       packet(7)

AF_ALG              Interface to kernel crypto API

#include <sys/types.h>          /* See NOTES */

#include <sys/socket.h>

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

struct sockaddr {

   sa_family_t sa_family;

   char        sa_data[14];

}

#include <sys/types.h>          /* See NOTES */

#include <sys/socket.h>

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

#define _GNU_SOURCE             /* See feature_test_macros(7) */

#include <sys/socket.h>

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

#include <sys/types.h>          /* See NOTES */

#include <sys/socket.h>

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

2.API介绍

(1)#include<sys/socket.h>

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

参数详解:

domain 参数

domain 参数指定套接字的协议族,常见的协议族有 AF_INET(IPv4)、AF_INET6(IPv6)和 AF_UNIX(本地套接字)等。

type 参数

type 参数指定套接字的类型,常见的类型有 SOCK_STREAM(流套接字/TCP 套接字)和 SOCK_DGRAM(数据报套接字/UDP套接字)等。

protocol 参数

protocol 参数指定使用的协议,常见的协议有 IPPROTO_TCP(TCP 协议)和 IPPROTO_UDP(UDP 协议)等。如果指定为 0,则会根据 domain 和 type 参数自动选择协议。

返回值:

socket 函数的返回值为新创建的套接字的文件描述符,如果创建失败则返回 -1。

2)地址与端口设置的结构体 sockaddr_in

#include <netinet/in.h>

   

struct sockaddr_in{

     unsigned short         sin_family;    

     unsigned short int     sin_port;      

     struct in_addr         sin_addr;      

     unsigned char          sin_zero[8];   

};

struct in_addr{

     unsigned long     s_addr;  // 这是一个无符号32位整数,用于存储IPv4地址

};

/*

sin_family表示地址类型,对于基于TCP/IP传输协议的通信,该值只能是AF_INET;

sin_prot表示端口号,例如:21 或者 80 或者 27015,总之在0 ~ 65535之间;

sin_addr表示32位的IP地址,例如:192.168.1.5 或 202.96.134.133;

sin_zero表示填充字节,一般情况下该值为0;

Socket数据的赋值实例:

*/

// 实例

 struct sockaddr_in addr;

 memset(&addr, 0, sizeof(addr));  // 将结构体清零  主要是sin_zero表示填充字节为零

 addr.sin_family = AF_INET; //(TCP/IP – IPv4)

 addr.sin_port = htons(port_out);    // 绑定端口号 htons将一个无符号短整型数值转换为网络字节序

 addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 绑定IP htonl就是把ip字节顺序转化为网络字节顺序

                                            

// INADDR_ANY 泛指机器所有的IP因为有些电脑不只有一个网卡;INADDR_ANY 是一个宏,表示“任意地址”,通常用于服务器在绑定套接字时指定其愿意监听的地址。通常被赋值为 0.0.0.0 的网络字节序表示。

3)发送数据接口

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

参数:

sockfd:套接字描述符

buf:待要发送的数据

len:待要发送数据的长度

flags:0为阻塞发送,MSG_OOB为发送带外数据; MSG_DONTWAIT 或 MSG_NONBLOCK为非阻塞模式

带外数据:即在紧急情况下所产生的数据,会越过前面进行排队的数据优先进行发送。

返回值:

成功则返回的字节数量,失败则返回-1。

4)接收数据接口

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

参数:

sockfd:套接字描述符

buf:将接收到的数据放到buf中

len:接收数据的长度

flags:0为阻塞接收,即如果没有发送数据,则recv接口会阻塞; MSG_DONTWAIT 或 MSG_NONBLOCK为非阻塞模式

返回值:

>0:正常接收了多少字节的数据

==0:对端将连接关闭(即对端机器调用了close函数关闭了套接字)

<0:接收失败

3.在网络编程中write 和 send 函数区别:

(1)适用范围

     write 函数:是 Unix 系统中的系统调用函数,用于向文件或管道等文件描述符写入数据。虽然它也可以用于套接字编程,但在套接字编程中,它通常被视为较为基础的发送方式。

     send 函数:是 POSIX 标准中定义的函数,专门用于套接字编程。它提供了更多的控制和选项,更适合于复杂的网络通信需求。

(2)参数差异

     write 函数:其参数通常包括文件描述符(fd)、指向要写入数据的缓冲区的指针(buf)以及要写入的数据量(nbytes)。它没有提供额外的控制选项。

     send 函数:除了包含与 write 类似的参数(套接字文件描述符 sockfd、指向数据缓冲区的指针 buf、要发送的数据长度 len)外,还包含了一个额外的 flags 参数。这个 flags 参数允许调用者指定发送数据时的各种选项,如非阻塞模式、紧急数据等。

(3)发送数据的处理方式

     write 函数:在写入数据时,如果数据量较大,可能会被分成多次写入,需要多次调用 write 函数。此外,write 函数在写入过程中可能会受到信号的影响而被中断。

     send 函数:虽然 send 函数也可能需要多次调用以发送大量数据,但它提供了更好的可控性。通过设置 flags 参数,可以避免被信号中断,并且可以使用非阻塞模式等特性来优化发送过程。

三.代码示例

(1)创建TCP服务器

#include <stdio.h>

#include <unistd.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <dirent.h>

#include <stdlib.h>

#include <pthread.h>

#include <semaphore.h>

#include <signal.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <netinet/in.h>

/*

TCP服务器创建步骤:

1. 创建socket套接字(类似于open打开文件一样)

2. 绑定端口号和IP地址

3. 设置监听等待队列的数量

4. 等待客户端连接

5. 完成正常数据收发

0x1234

192.168.1.123

255.255.255.255

*/

int main(int argc,char **argv)

{   

    if(argc!=2)

    {

        printf("./app <端口号>\n");

        return 0;

    }

    int sockfd;

    /*1. 创建socket套接字*/

    sockfd=socket(AF_INET,SOCK_STREAM,0);

    /*2. 绑定端口号与IP地址*/

    struct sockaddr_in addr;

    addr.sin_family=AF_INET;

    addr.sin_port=htons(atoi(argv[1])); // 端口号0~65535

    addr.sin_addr.s_addr=INADDR_ANY;    //inet_addr("0.0.0.0"); //IP地址

    if(bind(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr))!=0)

    {

        printf("服务器:端口号绑定失败.\n");

    }

    /*3. 设置监听的数量*/

    listen(sockfd,20);

    /*4. 等待客户端连接*/

    int client_fd;

    struct sockaddr_in client_addr;

    socklen_t addrlen=sizeof(struct sockaddr_in);

    client_fd=accept(sockfd, (struct sockaddr *)&client_addr,&addrlen);

    if(client_fd<0)

    {

        printf("客户端连接失败.\n");

        return 0;

    }

    printf("连接的客户端IP地址:%s\n",inet_ntoa(client_addr.sin_addr));

    printf("连接的客户端端口号:%d\n",ntohs(client_addr.sin_port));

    /*5. 完成通信*/

    write(client_fd,"1234567890",10);

    /*6. 关闭连接*/

    close(client_fd);

    close(sockfd);

    return 0;

}

(2)创建TCP客户端

#include <stdio.h>

#include <unistd.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <dirent.h>

#include <stdlib.h>

#include <pthread.h>

#include <semaphore.h>

#include <signal.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <netinet/in.h>

/*

TCP客户端创建步骤:

1. 创建socket套接字(类似于open打开文件一样)

2. 连接服务器

3. 完成正常数据收发

*/

int main(int argc,char **argv)

{   

    if(argc!=3)

    {

        printf("./app  <IP地址> <端口号>\n");

        return 0;

    }

    int sockfd;

    /*1. 创建socket套接字*/

    sockfd=socket(AF_INET,SOCK_STREAM,0);

    /*2. 连接服务器*/

    struct sockaddr_in addr;

    addr.sin_family=AF_INET;

    addr.sin_port=htons(atoi(argv[2])); // 端口号0~65535

    addr.sin_addr.s_addr=inet_addr(argv[1]); //IP地址

    if(connect(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr_in))!=0)

    {

        printf("客户端:服务器连接失败.\n");

        return 0;

    }

    /*3. 完成数据通信*/

    char buff[1024];

    int cnt;

    cnt=read(sockfd,buff,1024);

    buff[cnt]='\0';

    printf("客户端收到的数据:%s,%d\n",buff,cnt);

    

    close(sockfd);

    return 0;

}

08-29 03:45