TCP/IP网络编程(理解网络编程和套接字)

发布于 2021-12-09  162 次阅读


1.1 理解网络编程和套接字

网络编程和套接字概要

网络编程就是编写使两台连网的计算机相互交换数据。
操作系统回提供名为套接字(socket)的部件。套接字是网络数据传输用的软件设备。
网络编程又称为套接字编程。
“套接字”就是用来连接网络的工具。他本省就带有“连接”的含义。

构建电话套接字

套接字大致分为两种,TCP套接字可以比喻成电话机。
电话机也是通过固定电话网(telephone network)完成语音数据交换的。
电话机可以同时用来拨打或接听,但对套接字而言,拨打和接听是有区别的。
接听套接字创建过程。
调用socket函数(安装电话机)时进行的对话
- 问:“接电话需要准备什么?”
- 答:“当然是电话机!”。
有了电话机才能安装电话。
准备一部漂亮的电话机。下列函数创建的就是相当于电话机的套接字。
#include<sys/socket.h>    //linux系统里面是这样子的
//#include <winsock.h>
int socket(int domain,int type,int protocol);
//成功时返回文件描述,失败时返回-1
准备好电话机后,需要考虑分配电话号码的问题,这样瘪人才能联系到自己。
调用bind函数(分配电话号码)时进行的对话
问:“请问宁的电话号码是多少?”
答:“我的电话号码是xxxxxx”。
套机字同样如此。
就像给电话机分配电话号码一样,利用下面函数给创建好的套接字分配地址信息(IP地址和端口号)
#include<sys/socket.h>
int bind(int sockfd,struct sockaddr *myaddr,socklen_t addrlen);
//成功返回0,失败返回-1。
//调用bind函数给套接字分配地址后,就基本完成了接电话的所有准备工作。
//接下来需要连接电话线并等待来电。
调用listen函数(连接电话线)时进行的对话
- 问:“已架设完电话机后是否只需要连接电话线?”
- 答:“对,只需要连接就能接听电话”
//一连接电话线,电话机就转为可接听状态,这时其他人可以拨打电话请求连接到该机。
//同样,需要把套接字转换称为可接收连接的状态。
#include<sys/socket.h>
int listen(int sockfd,int backlog);
//成功时返回0,失败时返回-1。
//接好电话先,如果有人拨打电话就会响铃,拿起话筒才能接听电话
调用accept函数(拿起话筒)时进行的对话
- 问:“电话铃响了,我该怎么办?”
- 答: “接听啊!”
拿起话筒意味者接收了对方的连接请求。
套接字同样如此,如果有人为了完成数据传输而请求连接,就需要调用一下函数进行受理
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
//成功时返回文件描述符,失败时返回-1。

网络编程中接收连接请求的台阶子创建过程可整理如下:
第一步: 调用socket函数创建套接字
第二部: 调用bind函数分配ip地址和端口号
第三步: 调用listen函数转换为可接受请求状态。
第四步: 调用accept函数受理连接请求。

编写“hello world!”服务器端

服务器端(serve)是能够受理连接请求的程序。下面构建服务器端以验证之前提到的函数调用过程。
该服务器端收到连接请求后向返回者返回“Hello world!”答复。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<windows.h>
void error_handling(char *message);

int main(int argc,char *argv[])
{
    int serv_sock;
    int clnt_sock;

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size;

    char message[]="hello world!";

    if(argc!=2)
    {
        printf("Usage : %s <port>\n",argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET,SOCK_STREAM,0);  //调用socket函数创建套接字 
    if(serv_sock == -1)
        error_handling("socket() error");

    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)  //调用bind函数分配IP地址和端口号 
        error_handling("bind() error");

    if(listen(serv_sock,5)==-1)         //调用listen函数将套接字转为可接收连接状态 
        error_handling("listen() error");

    clnt_addr_size = sizeof(clnt_addr);
    clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_size);   //调用accept函数受理连接请求。如果没有连接请求的情况下调用该函数,则不会返回,直到有连接请求为止 
    if(clnt_sock==-1)
        error_handling("accept() error");

    write(clnt_sock,message,sizeof(message));   //write函数用于传输数据,若程序经过42行代码执行到本行,则说明已有连接请求。 
    close(clnt_sock);
    close(serv_sock);
    return 0;
}
void error_handling(char *message)
{
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}

构建打电话套接字

服务器端床间的套接字又称为服务器端套接字或着监听(listening)套接字。
接下来的套接字是用于请求连接的客户端套接字。客户端套接字的创建过程比创建服务器端套接字简单。
//打电话(请求连接)的函数,因为其调用的是客户端套接字。
#include<sys/socket>
int connect(int sockfd,struct sockaddr *serv_addr,socklen_t addrlen);
//成功时返回0,失败时返回-1.
客户端程序只有“调用socket函数创建套接字”和“调用connect 函数向服务器端发送连接请求”这两个步骤,因此比服务器端简单。
下面给出客户端,查看以下两项内容:
第一,调用socket函数和connect函数;
第二,与服务器端共同运行以收发字符串数据
//hello_client.c
//hello_client.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
void error_handling(char *message);

int main(int argc,char *argv[])
{
    int sock;
    struct sockaddr_in serv_addr;
    char message[30];
    int str_len;

    if(argc!=3)
    {
        printf("Usage : %s <ip> <port>\n",argv[0]);
        exit(1);
    }
    sock = socket(PF_INET,SOCK_STREAM,0); //创建套接字,但是此时套接字并不马上分为服务器端和客户端。如果紧接着调用bind、listen函数,将成为服务器端套接字;如果调用connect函数,将会成为客户端套接字。 
    if(sock == -1)
        error_handling("socket() error");

    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)    //调用connect函数想服务器端发送连接请求
        error_handling("connect() error!");

    str_len = read(sock,message,sizeof(message)-1);
    if(str_len==-1)
        error_handling("read() error!");

    printf("Message from server: %s \n",message);
    close(sock);
    return 0;
} 

void error_handling(char *message)
{
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}

在Linux平台下运行

Linux下的C语言编译器——— GCC(GNU Compiler Collection,GUN编译器集合)。

gcc hello_server.c -o hello_server
#编译hello_server.c文件生成可执行文件hello_server
./hello_server
#运行hello_server文件

运行程序:hello_server
file
同样的方法运行编译hello_client.c后运行客户端
file
完成消息传输后,服务器端和客户端都停止运行。

提示:
上面的服务器端无法立即重新运行。
如果想再此运行,则需要更改之前输入的端口号9190.

擦肩而过的概率