网络编程基本函数总结


计算机网络编程

一 Socket套接字、IP地址、Port端口号的定义

1. Socket套接字

在Unix/Unix-like操作系统中有一句名言:一切皆文件。所以Socket本身就是一个文件。

运行中的计算机中存在很多已经打开的文件,为了标识和区分这些文件,操作系统为每一个文件分配了一个ID,这个ID本质上就是一个整数,我们称这个ID为文件描述符(file descriptor)。

所以既然Socket本质上是一个文件,那它同样有一个文件描述符。

我们采用socket()函数来创建套接字,本质上是创建一个文件,函数返回值是一个整形数字(其实就是文件描述符),我们用返回值来表示套接字。

在Windows中,将socket看成一个网络链接,创建socket后也就是网络链接之后,同样返回一个东西,我们称之为句柄(很熟悉有木有,汇编语言中重头戏就是句柄呀)

2. IP地址

从来就没有一个主机只有一个IP地址的说法!

站在第三层,也就是网络层来看,互联网是由许多的局域网和路由器组成,IP地址的作用就是确定目的主机在互联网的哪一个局域网中,到达主机所在的局域网之后,通过ARP地址解析协议我们进行IP地址和物理Mac地址之间的转换,最终我们确定主机是通过Mac硬件地址。

3. 端口

我们可以根据IP地址和Mac地址到达服务器,但是一个服务器中存在很多个端口,每一个应用程序对应着一个端口,那么为了标识和区分这些端口,我们就对这些端口进行编号,最终得到端口号。

端口号是一个16位的无符号整形,因此我们可以知道最大为 2^ 16也就是65536, 其中0~1024的端口号系统分配给系统常用的程序,所以进行计算机网络编程时,使用的端口号要大于1024。

注意这里的端口是一个虚拟的、逻辑上的概念,并不是物理正是存在的。


二 通过socket()函数创建一个套接字

在这里插入图片描述
我们先分析函数的参数

(1) domain 指的是地址族 (address family) 或者说时IP地址的类型,其中常用的有:AF_INET、AF_IETF6分别对应的是IPv4和IPv6地址类型。

(2) type 指的是套接字的类型,常见的有:SOCK_STREAM、SOCK_DGRAM,前者数据传输方式对应的是TCP协议、后者对应的是UDP传输协议。

(3) protocol 指的是传输协议,其中常见的有IPPTOTO_TCP、IPPTOTO_UDP,分别对应的是TCP传输协议和UDP传输协议,一般来说前两个参数确定了,第三个参数就定下来了,但是确保安全,还是引入第三个参数。

前面说过socket本质上是一个文件,因此函数返回的其实就是socket文件的文件描述符。若创建socket失败,函数返回的是-1。


三 服务器端采用bind()函数将socket与IP地址和port端口号绑定

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
创建socket后,服务器端需要将socket与IP和Port进行绑定,若绑定成功,函数返回0,失败,函数返回-1。

这样流经该IP地址和端口的数据才会交给套接字处理。注意:这里的IP地址和端口号是服务器本地的IP地址和端口号。

(1) sockfd: 是socket文件的文件描述符

(2) struct sockaddr *:结构体指针,我们需要绑定的IP地址和port端口号就是存储在指针对应的结构体中的。

(3) socketlen_t addrlen:第二个参数结构体的大小,可以使用sizeof()关键字,注意此处只可以是结构体变量或者是该结构体类型,不可以是指向该结构体的指针。

虽然第二个参数类型上我们采用struct sockaddr 但实际上采用的是struct sockaddr_in,两者所占字节数都是16个字节,因此可以采用强制转换。有的说法说 struct sockaddr 更通用,可以保存多种类型的IP地址和端口,而struct sockaddr_in是用来保存IPv4网络的IP地址和端口。

其中关于struct sockaddr_in中的每个成员分别代表什么,应该了解清楚。

//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

//创建sockaddr_in结构体变量
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);  //端口

//将套接字和IP、端口绑定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
struct sockaddr_in{
    sa_family_t     sin_family;   //地址族(Address Family),也就是地址类型
    uint16_t        sin_port;     //16位的端口号
    struct in_addr  sin_addr;     //32位IP地址
    char            sin_zero[8];  //不使用,一般用0填充
};

struct in_addr{
    in_addr_t  s_addr;  //32位的IP地址
};

IP地址是字符串类型,所以需要inet_add()函数把字符串类型,转换为整形

htons()函数,将整形的主机字节序转换为网络字节序


四 connect()与accept()函数

在这里插入图片描述
connect()函数是用户端创建socket之后要进行的工作,用来与指定主机之间建立连接,所以struct sockaddr结构体中的存储的IP地址应该是目的主机的IP地址和端口号

函数的整个参数和返回值与bind()函数是一致的。

ACCEPT()

服务器端根据accept()函数接受客户端的请求,除函数名外,函数参数、函数返回值和connect()都是一致的。

需要注意一点:客户端创建socket并将socket与IP地址和端口绑定之后,并不是直接就可以接收客户端的请求的,中间还需要listen()函数。

此外,函数会返回一个新的套接字,后面与客户端进行通话时,采用的是新返回的套接字,而不是之前服务端生成的套接字。


五 listen()函数

在这里插入图片描述

若函数执行成功,返回0,否则返回-1,函数的作用是使之前服务器端创建的套接字进入被动监听状态(所谓被动监听状态:当客户端没有请求时,套接字会处于“睡眠状态”,当接收到客户端的请求时,套接字才会被唤醒)

(1) sockfd 创建的socket对应的文件描述符

(2) backlog 缓冲等待队列的长度,当套接字正在处理某一个客户端请求时,若此是有新的请求进来,那么将会被放到缓冲队列中等待。处理完当前请求后,会从缓冲区中取出下一个请求,处理顺序采用先来先服务的理念。当请求队列满后,服务器段就不会在接受新的请求,客户端也会收到ECONNREFUSED错误。

再次强调listen()函数,只是让socket处于监听状态,并没有接受请求,接受请求是通过accept()函数实现的。


六 send()和recv()函数

在这里插入图片描述
在这里插入图片描述
send()和recv()函数很类似

(1) sockfd (socket file descriptor) 套接字文件描述符,对于客户端,这个套接字文件描述符就是之前自己创建的,但是对于服务器端而言,采用的并不是之前服务器创建的套接字,而是accept()函数的返回值。

(2) void *buf,发送内容的指针,常见的是字符串指针

(3) len 发送内容长度,再次强调,采用sizeof()关键字获取大小时,其中参数若是指向某字符串的指针,最终得到的大小是这个指针的大小,而不是整个字符串所占的字节。

(4) flags 不要管,直接设置为0即可。

函数的返回值是函数发送或接受内容的字节数,若执行失败,函数返回-1。


七 close()关闭连接

在这里插入图片描述
close() 关闭连接,成功返回0,失败返回-1。函数的参数为socket文件描述符


八 getpeername()函数获取对端地址

在这里插入图片描述

client 端代码:

#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>


int main(int argc, char **argv) {
    int sockfd, port;
    struct sockaddr_in server;
    if (argc != 3) {
        fprintf(stderr, "Usage: %s ip port\n", argv[0]);
        exit(1);
    }
    port = atoi(argv[2]);

    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = inet_addr(argv[1]);

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket");
        exit(1);
    }

    printf("Socket create\n");
    if (connect(sockfd, (struct sockaddr *)(&server), sizeof(server)) < 0) {
        perror("connect");
        exit(1);
    }
    sleep(0);
    if (send(sockfd, "zhangsan", sizeof("zhangsan"), 0) < 0) {
        perror("send");
        exit(1);
    }
    close(sockfd);

    return 0;
}

server端代码:

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


int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s part\n", argv[0]);
        exit(1);
    }
    
    int port; //端口
    int server_listen;
    port = atoi(argv[1]);

    if ((server_listen = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket");
        exit(1);
    }
    
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_listen, (struct sockaddr *)(&server), sizeof(server)) < 0) { 
        perror("bind");
        exit(1);
    }

    if (listen(server_listen, 20) < 0) {
        perror("listen");
        exit(1);
    }
    
    while (1) {
        int sockfd;
        printf("Socked before accept\n");
        if ((sockfd = accept(server_listen, NULL, NULL)) < 0) {
            perror("accept");
            close(sockfd);
            continue;
        }
       
        printf("sockfd = %d\n", sockfd);

        char name[20] = {0};
        if (recv(sockfd, name, sizeof(name), 0) <= 0) {
            close(sockfd);
            continue;
        }
        printf("Socked after accept\n");
        printf("name: %s\n", name);
        close(sockfd);
    }

    return 0;
}

注意:server端会产生僵尸进程,代码有待改进!

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值