Linux——网络编程

本文详细介绍了网络编程的基础知识,包括为什么需要网络编程,以及如何使用Socket进行服务器和客户端的开发。讲解了创建套接字、绑定地址、监听连接、接受连接、数据收发等关键步骤,并提供了服务器端和客户端的代码示例,帮助理解Socket在多机通讯中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


一、为什么需要网络编程?

需要实现多机通讯

回射客户/服务器

  1. 当套接字被创建后,它的端口号和IP地址都是空的,因此应用进程要调用bind(绑定)来指明套接字的本地地址。在服务器端调用bind时就是把熟知端口号和本地IP地址填写到已创建的套接字中。这就叫做把本地地址绑定到套接字。
  2. 服务器在调用bind中,还必须调用listen(收听)把套接字设置为被动方式,以便随时接受客户的服务请求。UDP服务器由于只提供无连接服务,不使用listen系统调用。
  3. 服务器紧接着就调用accept(接受),以便把远端客户进程发来的连接请求(connect)[主动方式]提取出来。系统调用accept的一个变量就是要指明从哪个套接字发起的连接。

二、Sockt服务器和客户端开发步骤

在这里插入图片描述

服务器端

socket
网络上的两个应用程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为socket。socket可以看成是用户进程与内核网络协议栈的编程接口。socket可以看成两个进程之间通信的抽象。socket是全双工的通信方式。
socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机(异构也可以)的进程间通信。

1.创建套接字

创建套接字的作用

1.当应用进程需要使用网络进行通信时就发出系统调用,请求操作系统为其创建”套接字”,以便把网络通信所需要的系统资源分配给该应用进程。
2.操作系统为这些资源的总和用一个叫做套接字描述符的号码来表示,并把此号码返回给应用进程。应用进程所进行的网络操作都必须使用这个号码。
3.通信完毕后,应用进程通过一个关闭套接字的系统调用通知操作系统回收与该”号码”相关的所有资源。

socket函数

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

int socket(int domain, int type, int protocol);
// domain:指定通信协议族(protocol family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用IPV4地址与端口号的组合。
// type:指定socket类型。流式套接字(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报套接字(SOCK_DGRAM),针对于无连接的UDP服务应用;原始套接字(SOCK_RAW)。
// protocol:协议类型。常用的协议有IPPROTO_TCP(Tcp传输协议)、IPPROTO_UDP(Udp传输协议)、IPPROTO_STCP(Stcp传输协议)、IPPROTO_TIPC(Tipc传输协议)等。

返回值:成功返回非负整数,也就是套接口描述字,简称套接字,失败返回-1.每一个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构对应的关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。

2.准备地址

bind函数

功能:绑定一个本地地址到套接字
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,
         socklen_t addrlen);
参数:sockfd:socket函数返回的套接字
     addr:要绑定的地址
     addrlen:地址长度

在这里插入图片描述

3.地址转换

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/*
功能:将一个字符串IP地址转换为32为的网络序列IP地址。
返回值:函数成功,函数返回值非零,如果输入地址不正确会返回零。
参数1:输入参数 cp const char* 点分十进制
参数2:输出参数 in struct in_addr 无符号长整型

aton表示ascii码转换为net 也就是点分十进制转换为网络字节序
*/
int inet_aton(const char *cp, struct in_addr *inp); 

/*
返回值:若字符串有效则将字符串转换为无符号长整型格式,否则为INADDR_NONE
*/
in_addr_t inet_addr(const char *cp); // 点分十进制IP转换成十进制

/*
返回值:无符号长整型转换为ascii码格式,也就是点分十进制
*/
char *inet_ntoa(struct in_addr in);  // 十进制转换成点分十进制,输出是字符串格式

/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

4.监听

listen函数

功能:将套接字用于监听进入的连接
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

参数1:sockfd:socket函数返回的套接字
参数2:backlog:规定内核为此套接字排队的最大连接个数
返回值:成功返回0 失败返回-1

一般来说,listen函数应该在调用socket和bind函数之后,调用函数accept之前调用。
对于给定的监听套接口,内核要维护两个队列:

  1. 已由客户端发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程
  2. 已完成连接的队列

5.连接

accept函数

功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

// 参数1:sockfd 服务器套接字
// 参数2:addr 将返回对等方的套接字地址
// 参数3:addrlen 返回对等方的套接字地址长度
// 返回值:成功返回非负整数(客户端文件描述符),失败返回-1

数据的收发

read函数

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数1:文件描述符
参数2:读取到的字节数放置的地址buf
参数3:请求从文件描述符fd读取的字节数,读取到buf
返回值:成功,返回读取的字节数;如果缓冲区中的字节数不够,读取的字节数可能比count个字节小少。错误,返回-1,同时errno将会被设置。同时文件的读写位置向后移。

write函数

功能:从buffer中写count个字节到打开的文件中(fd表示打开的文件)
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数1:文件描述符
参数2:将要被写出到其他文件的缓冲区内容
参数3:count表示写出字节数
返回值:成功,写出的字节数返回(0表示没有可以写的了),如果写出的字节数比count小,可能是buf中没有count个字节;错误,返回-1.

write成功返回,只是buf中的数据被复制到内核中的TCP发送缓冲区。置于数据什么时候被发往网络,什么时候被对方主机接收,什么时候被对方进程读取,系统调用层面不会给予任何保证和通知。

服务器端代码演示

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

int main()
{
        //1.socket
        int s_fd;
        struct sockaddr_in s_addr;       //定义服务器地址结构体
        struct sockaddr_in c_addr;       //客户端地址结构体
        int n_read;
        char readBuf[128]={0};
        char *wr="it's for server";

        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));


        s_fd=socket(AF_INET,SOCK_STREAM,0);  //第一步创建套接字 以及套接字描述符 后续其他操作依靠描述符来操作 和 文件描述符类似
        if(s_fd==-1){   //如果等于-1 创建失败
                perror("socket");   //调用perror函数 显示错误原因
                exit(-1);   
        }
        //2.bind
        s_addr.sin_family=AF_INET;   //结构体第一个属性 为 网络属性 上面有介绍
        s_addr.sin_port=htons(9809);   //定义端口号  需注意 将端口转为 网络字节序
        //s_addr.sin_addr=192.168.0.168;
        inet_aton("192.168.0.108",&s_addr.sin_addr); // 这里需要将地址ASCII转成net 第二个参数为指针 放着结构体第三个属性 IP地址

        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
 		//绑定地址  第二个参数是个结构体指针 包括IP地址 端口信息   第三个为结构体大小
        //3.listen
		listen(s_fd,10); //监听  第二个参数 为 监听数量
        //4.accept
        int clen=sizeof(struct sockaddr_in);
        int c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&clen); //从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
        //第二个参数 和bind类似 第三个参数 是个指针 放对接地址的长度
        //成功返回客户端的文件描述符 失败返回-1
        if(c_fd==-1){
                perror("accept");
        }
        printf("get connect ip:%s\n",inet_ntoa(c_addr.sin_addr));  //将对接地址的net转成ASCII
        //read
        n_read=read(c_fd,readBuf,128); //将c_fd内容读到readbuf
        if(n_read==-1){
                perror("read");
        }else{
                printf("get message size:%d,%s\n",n_read,readBuf); //打印readbuf
        }
        //write
        write(c_fd,wr,strlen(wr));

        return 0;
}

客户端

connect函数

// 功能:用于建立与指定socket的连接
int connect(int s, const struct sockaddr * name, int namelen);

参数1:s 标识一个未连接的socket
参数2:name 指向要连接套接字的sockaddr结构体指针,也就是sockaddr中是要连接的服务器端的地址。
参数3:sockaddr 结构体的字节长度

客户端代码演示

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

int main()
{
        //1.socket
        int c_fd;
        struct sockaddr_in c_addr;
        int n_read;
        char readBuf[128]={0};
        char *wr="it's for client";

        memset(&c_addr,0,sizeof(struct sockaddr_in));


        c_fd=socket(AF_INET,SOCK_STREAM,0);// 创建客户端套接字
        if(c_fd==-1){
                perror("socket");
                exit(-1);
        }
        
        c_addr.sin_family=AF_INET;
        c_addr.sin_port=htons(9809);
        //s_addr.sin_addr=192.168.0.168;
        inet_aton("192.168.0.108",&c_addr.sin_addr);
        //connect
        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in))==-1){
        //连接  第二三个参数 和bind类似
                perror("connect");
                exit(-1);
        }
		printf("get connect ip:%s\n",inet_ntoa(c_addr.sin_addr));
        write(c_fd,wr,strlen(wr));  //往c_fd写入

        //read
        n_read=read(c_fd,readBuf,128);
        if(n_read==-1){
                perror("read");
        }else{
                printf("get message size:%d,%s\n",n_read,readBuf);
        }
        //write

        return 0;
}

实现多机通讯

server

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

int main(int argc,char **argv)  //这里用命令行参数 带代替输入 IP地址 端口号
{
        //1.socket
        int s_fd;
        int c_fd;
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;
        int n_read;
        char readBuf[128]={0};
        //char *wr="it's for server";
        char wr[128]={0};
        int mark=0;

        if(argc !=3){   //做个判断 防止少输 多输
                perror("arg");
                exit(-1);
        }

        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));


        s_fd=socket(AF_INET,SOCK_STREAM,0);
        if(s_fd==-1){
                perror("socket");
                exit(-1);
        }
//2.bind
        s_addr.sin_family=AF_INET;
        s_addr.sin_port=htons(atoi(argv[2]));  //这里将命令行输入的字符串转成int型 再通过htons转成网络字节序
        //s_addr.sin_addr=192.168.0.168;
        inet_aton(argv[1],&s_addr.sin_addr);

        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
        //3.listen
        listen(s_fd,10);
        //4.accept
        int clen=sizeof(struct sockaddr_in);
        while(1){//做个循环不停的accept connect的请求
                c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&clen);
                if(c_fd==-1){
                        perror("accept");
                }
                printf("get connect ip:%s\n",inet_ntoa(c_addr.sin_addr));
                //read
                mark++;   // 定义mark变量 显示这是第几个客户端
                if(fork()==0){

                        if(fork()==0){
                                while(1){
                                        memset(wr,0,sizeof(wr));   //循环会不停的调用wr 里面的内容要重置 防止覆盖
                                        sprintf(wr,"welcome NO.%d Client\n",mark); //服务端每隔3秒打印 写入wr
                                        write(c_fd,wr,sizeof(wr));  //写入 供客户端读取 
                                        sleep(3);
								}
                        }
                        while(1){
                                memset(readBuf,0,sizeof(readBuf));  //读也要重置
                                n_read=read(c_fd,readBuf,128);    
                                if(n_read==-1){
                                        perror("read");
                                }else{
                                        printf("get message size:%d,%s\n",n_read,readBuf);
                                }
                                //break;

                        }
                }

        }
        return 0;
}

client

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

int main(int argc,char ** argv)
{
        //1.socket
        int c_fd;
        struct sockaddr_in c_addr;
        int n_read;
        char readBuf[128]={0};
        char wr[128]={0};
        if(argc !=3){
                perror("arg");
                exit(-1);
        }

        memset(&c_addr,0,sizeof(struct sockaddr_in));


        c_fd=socket(AF_INET,SOCK_STREAM,0);
        if(c_fd==-1){
                perror("socket");
                exit(-1);
        }
        //2.bind
        c_addr.sin_family=AF_INET;
        c_addr.sin_port=htons(atoi(argv[2]));
        //s_addr.sin_addr=192.168.0.168;
        inet_aton(argv[1],&c_addr.sin_addr);
        //3.connect
        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in))==-1){
                perror("connect");
                exit(-1);
        }


        printf("get connect ip:%s\n",inet_ntoa(c_addr.sin_addr));

        while(1){
                if(fork()==0){
                        while(1){
                                memset(wr,0,sizeof(wr));
                                printf("clien input: ");
                                gets(wr);
                                write(c_fd,wr,strlen(wr));
                        }
                }



                while(1){
                        n_read=read(c_fd,readBuf,128);
                        if(n_read==-1){
                                perror("read");
                        }else{
                                printf("get message size:%d,%s\n",n_read,readBuf);
                        }
                        //write
                }
        }

        return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值