Linux 网络编程

引入:


进程间的通信 - 特点: 依赖 Linux内核. --> 缺陷: 无法多机通信

多机通信 -- 比如 Android IOS Linux之间的通信组合

网络编程:

1.地址: 
  a.IP地址
  b.端口号

2.数据:

需要协议传输协议(数据格式)(TCP UDP HTTP) ,其他协议,such as: C51的串口协议

socket 套接字:


TCP: 面向连接: 打电话 -- 适用精细操作

UDP:    面向报文: (无连接)发数据 -- 适用大量数据,且不要求精细

TCP 和 UDP 对比 :


1.TCP面向连接(比如,打电话,需要先输入号码建立连接)
   UDP无需连接
2.TCP提供可靠服务 -- 通过TCP传输的数据,无差错,不丢失,不重复,且程序可达
   UDP尽最大可能交付,但不保证交付
3.TCP面向字节流,实际上是TCP把数据看成一串无结果的字节流
   UDP面向报文,UDP没有拥塞控制,so网络出现拥塞不会使得元主机发送速率降低(对实时应用很有用,IP电话,实时视频会议等)
4.连接上: TCP: 一对一,点到点
               UDP:一对一,一对多,多对一,多对多的交互通信
5.TCP首部开销大,   20字节
   UDP首部开销小,  8字节
6.逻辑通信信道上,TCP: 双全工   
                               UDP:不可靠信道

端口号的作用:


拥有IP的主机会提供一系列的服务,通过" IP地址+ 端口号"来区分不同的服务
端口提供了一种访问通道

服务器一般都是通过知名的端口号来识别。
比如: FTP服务器-TCP21  Telnet-TCP23  TFTP-UDP69
=====================================================


字节序

概念:
字节序 -- 多字节数据在 计算机内存中存储 或者 网络传输时 各字节的存储顺序

常见序:
小端字节序-LE:低序字节存储在起始地址
大端字节序-BE:  高序字节存储在起始地址
 
比如:  0x 01 02 03 04
内存地址从低字节算起, 就是从04 算起,04 - 4001 03-4002 02-4003 01-4004

LE(Little Endian) : 04 03 02 01
BE(Big Endian):   01 02 03 04

网络字节序 -- 大端字节序

Scoket 网络编程步骤:


TCP Server
服务器开发步骤:
socket() --> bind() --> listen() --> accept()  --> read() write() --> close()
1. 创建套接字 --socket
2.为套接字添加信息(IP 和 端口号) - bind
3. 监听网络连接   -->listen()
4.有客户端接入接受一个链接 - accept
5.数据交互 - read() write()
6.关闭套接字,断开连接 close()

TCP Client:

scoket() --> connect() --> write(),read() --> close()


====================================================


Linux 网络编程 API

服务器


1.创建套接字socket: 

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

函数原型

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

参数介绍: 


domain - 协议族:  AF_INET IPv4,  af_INET6  IPv6  - 因特网域  AF_UNIX -Unix域、

type-- 指定类型: 
 SOCK_STREAM  -- TCP-- 字节流的形式
 SOCK_DGRAM -- UDP -- 报文的形式

protocol  --协议: 
0 -- 选择 type指定类型的吗,默认协议
IPPROTO_TCP
IPPROTO_UDP

IPPROTO_SCTP
IPPROTO_TIPC


2.准备地址(IP + 端口号) 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 -- 结构体指针-- 结构体里面包含了需要的 IP  和 端口号 协议族
addrlen -- 结构体的大小

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填充
};

------------------------


地址 转换API:
 

头文件

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

函数原型

       int inet_aton(const char *cp, struct in_addr *inp);
     //将字符串形式的ip地址(102.168.88.5)转换为网络能识别的格式

    //  a -- ASCLL     to -- 就是to转换为的意思  n -- net  网络形式
 
         char *inet_ntoa(struct in_addr in);
    // 把网络格式的ip地址转为字符串形式的ip地址

3.监听:listen 

函数原型:
               int listen(int sockfd, int backlog);

参数:
 sockfd -- socket描述符
 backlog -- 最大连接数


4.连接accept:

函数原型
   int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:
 sockfd -- socket描述符
 addr --- 客户端地址
 addrlen); --  客户端地址长度


5. 数据收发:

用到API
      read() write()   send() recv()


客户端连接主机:

注:由于客户端的其他方面步骤使用的API 和服务器都一样,我们在这就不重复了,请看上面服务器API的部分去了解,我们这里只介绍客户端独有的  connect()

connect()
 

函数原型

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

参数:
*addr -- 服务器ip和端口号的指针
addrlen -- 什么的纠结体的大小
-------------------------------------------------------

 查看库的指令补充
 在 /usr/include/ 下去查找文件的时候
可使用 grep "test" * -nir  
-n 找到返回行号
-i 不区分大小写
-r递归寻找


//低于3000 的端口是操作系统使用,我们使用5000即可
我们Linux固定的ip:192.168.88.130(根据自己的ip地址修改)

怎么查看自己的ip地址  -- linux 指令  ifconfig




实例


case1: 实现基本的服务器:


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

int main()
{
    int s_fd; // 来接收套接字描述符
//1. socket -- IPv4  流形式i 0 --默认 TCP 
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){ // 返回值为-1 表示创建失败
    perror("socket");
    exit(-1);
}
// 初始化bind需要的struct
struct sockaddr_in s_addr;
s_addr.sin_family=AF_INET; // 指定协议族为IPV4
// htons -- h - host to  ns-net short
s_addr.sin_port=htons(8989);// 指定端口号
// a -- ASCLL 
inet_aton("192.168.88.130",&s_addr.sin_addr); // 指定ip地址,注意第二个参数格式,把ip地址放到第二个参数 sin_arr里面

//3.bind
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in ));// bind - 建立连接 准备
// listen
listen(s_fd,10); // listen 阻塞程序,等待连接  指定最大链接数为10
//4.accept
int c_fd=accept(s_fd,NULL,NULL); // 获得新的客户端 描述符

puts("connect.");
while(1);

    return 0;
}

============================================


case2 -- read write 实现读取输入信息

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


int main()
{
    int s_fd;
    struct sockaddr_in s_addr;
    struct sockaddr_in c_addr; // 创建两个结构体,自发自收

    int nread;
    char *msg="I get your connect";
    char readBuf[128];
    memset(&s_addr,0,sizeof(struct sockaddr_in));
    memset(&c_addr,0,sizeof(struct sockaddr_in));
int clen=sizeof(struct sockaddr_in);
//1. socket -- IPv4  流形式i 0 --默认 TCP 
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
    perror("socket");
    exit(-1);
}
// 初始化bind需要的struct

s_addr.sin_family=AF_INET;
// htons -- h - host to  ns-net short
s_addr.sin_port=htons(8988);
// a -- ASCLL 
inet_aton("192.168.88.130",&s_addr.sin_addr);

//3.bind
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in ));
// listen
listen(s_fd,10);
//4.accept  s_addr 发到 c_addr 里面
int c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&clen); // 获得新的客户端 描述符
if(c_fd == -1){
perror("accept");
}

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

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

// write
write(c_fd,msg,strlen(msg));

    return 0;
}
 

=============================


case3 : 编写代码实现客户端:

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


int main()
{
    int c_fd;
    struct sockaddr_in c_addr;
    int nread;
    char *msg="messeage from client";
    char readBuf[128];

    memset(&c_addr,0,sizeof(struct sockaddr_in));
int clen=sizeof(struct sockaddr_in);
//1. socket -- IPv4  流形式i 0 --默认 TCP 
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
    perror("socket");
    exit(-1);
}
// 初始化connect连接需要的struct

c_addr.sin_family=AF_INET;
// htons -- h - host to  ns-net short
c_addr.sin_port=htons(8988);
// a -- ASCLL 
inet_aton("192.168.88.130",&c_addr.sin_addr);

//2.connect -- 阻塞等待连接
int p_c=connect(c_fd,(struct sockaddr *)&c_addr,sizeof( struct sockaddr_in));
if(p_c==-1){
perror("connect");
exit(-1);
}

if(c_fd == -1){
perror("accept");
}

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

// write -- send 
write(c_fd,msg,strlen(msg));

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

    return 0;
}

===================================

case4: 双人聊天室

服务器

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

int main(int argc, char **argv)
{
    int s_fd;
    int c_fd;
    struct sockaddr_in s_addr;
    struct sockaddr_in c_addr;
    int nread;
    char msg[128]={0};
    char readBuf[128]={0};
    if(argc != 3){// 判断传参是否正确
    puts("Improper parameters");
    exit(-1);
    }
    memset(&s_addr, 0, sizeof(struct sockaddr_in));
    memset(&c_addr, 0, sizeof(struct sockaddr_in));
    int clen = sizeof(struct sockaddr_in);
    // 1. socket -- IPv4  流形式i 0 --默认 TCP
    s_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (s_fd == -1)
    {
        perror("socket");
        exit(-1);
    }
    // 初始化bind需要的struct

    s_addr.sin_family = AF_INET;
    // htons -- h - host to  ns-net short
    s_addr.sin_port = htons(atoi(argv[2])); // 通过传参指定端口
    // a -- ASCLL  传参方式得到IP
    inet_aton(argv[1], &s_addr.sin_addr);

    // 3.bind
    bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
    // listen
    listen(s_fd, 10);
    // 4.accept
    while (1)//保持一直接收,支持多个客户端的接入
    {
        c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen); // 获得新的客户端 描述符
        if (c_fd == -1)
        {
            perror("accept");
        }

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

        if (fork() == 0)
        { //  创建子进程来处理读写

            if (fork() == 0)
            {             // 创建一个子进程负责发送数据
                while (1) // 我们希望能一直接发
                {
                    memset(msg, 0, sizeof(msg));
                    printf("input:");
                    gets(msg); // //获取键盘数据,存入msg
                    write(c_fd, msg, strlen(msg));
                }
            }
            while (1)
            {
                // read()
                memset(readBuf,0,sizeof(readBuf));
                nread = read(c_fd, readBuf, 128);
                if (nread == -1)
                {
                    perror("read");
                }
                else
                {
                    printf("get message : %d ,%s\n", nread, readBuf);
                }
            }
        }
    }
    return 0;
}


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

int main(int argc, char **argv)
{
    int c_fd;
    struct sockaddr_in c_addr;
    int nread;
    if(argc != 3){// 判断传参是否正确
    puts("Improper parameters");
    exit(-1);
    }
    char msg[128] = {0};
    char readBuf[128];

    memset(&c_addr, 0, sizeof(struct sockaddr_in));
    int clen = sizeof(struct sockaddr_in);
    // 1. socket -- IPv4  流形式i 0 --默认 TCP
    c_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (c_fd == -1)
    {
        perror("socket");
        exit(-1);
    }
    // 初始化connect 需要的struct

    c_addr.sin_family = AF_INET;
    // htons -- h - host to  ns-net short
    c_addr.sin_port = htons(atoi(argv[2]));
    // a -- ASCLL
    inet_aton(argv[1], &c_addr.sin_addr);

    // 2.connect -- 阻塞等待连接
    int p_c = connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr_in));
    if (p_c == -1)
    {
        perror("connect");
        exit(-1);
    }

    if (c_fd == -1)
    {
        perror("accept");
    }

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

    while (1)
    { // 我们希望能一直读写 -- 实现聊天功能
        // write -- send
        if (fork() == 0)
        { // 创建一个子进程负责发送数据
            while (1) //我们希望能一直接发
            {
                memset(msg, 0, sizeof(msg));
                printf("input:");
                gets(msg); // //获取键盘数据,存入msg
                write(c_fd, msg, strlen(msg));
            }
        }
        while (1)//我们希望能一直接发
        {
            // read()
            memset(readBuf,0,sizeof(readBuf));
            nread = read(c_fd, readBuf, 128);
            if (nread == -1)
            {
                perror("read");
            }
            else
            {
                printf("get message from server: %d ,%s\n", nread, readBuf);
            }
        }
    }

    return 0;
}

=========================================


case5 : 实现多方消息接收:加个标志确定不同的客户端

每次连接到一个新的客户端mark就 + 1

// 通过mark 标记客户端是序号

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

int main(int argc, char **argv)
{
    int s_fd;
    int c_fd;
    int mark=0;
    struct sockaddr_in s_addr;
    struct sockaddr_in c_addr;
    int nread;
    char msg[128]={0};
    char readBuf[128]={0};
    if(argc != 3){// 判断传参是否正确
    puts("Improper parameters");
    exit(-1);
    }
    memset(&s_addr, 0, sizeof(struct sockaddr_in));
    memset(&c_addr, 0, sizeof(struct sockaddr_in));
    int clen = sizeof(struct sockaddr_in);
    // 1. socket -- IPv4  流形式i 0 --默认 TCP
    s_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (s_fd == -1)
    {
        perror("socket");
        exit(-1);
    }
    // 初始化bind需要的struct

    s_addr.sin_family = AF_INET;
    // htons -- h - host to  ns-net short
    s_addr.sin_port = htons(atoi(argv[2])); // 通过传参指定端口
    // a -- ASCLL  传参方式得到IP
    inet_aton(argv[1], &s_addr.sin_addr);

    // 3.bind
    bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
    // listen
    listen(s_fd, 10);
    // 4.accept
    while (1)
    {
        c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen); // 获得新的客户端 描述符
        if (c_fd == -1)
        {
            perror("accept");
        }
        mark++;// 每次连接到一个新的客户端mark就 + 1

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

        if (fork() == 0)
        { //  创建子进程来处理读写

            if (fork() == 0)
            {             // 创建一个子进程负责发送数据
                while (1) // 我们希望能一直接发
                {
                    memset(msg, 0, sizeof(msg));
                    sprintf(msg,"welcome NO.%d client",mark);
                    write(c_fd, msg, strlen(msg));
                    sleep(5);
                }
            }
            while (1)
            {
                // read()
                memset(readBuf,0,sizeof(readBuf));
                nread = read(c_fd, readBuf, 128);
                if (nread == -1)
                {
                    perror("read");
                }
                else
                {
                    printf("get message : %d ,%s\n", nread, readBuf);
                }
            }
        }
    }
    return 0;
}

===============================================================

后话


 为了解决不能多个窗口交流的问题,我们决定实现以下项目:

先提供思路和介绍,我们将=在下篇实现这个项目


实现ftp 服务器 客户端:
项目思路:

功能:

客户端:
1.获取服务器的文件  get xx
2.展示服务器的文件 ls
3.进入服务器的某个文件夹  cd 
4.上传文件到服务器   put


本地: 
1.ls  -- 查看本地文件
2.lcd 进入客户端 某某文件夹

详情了解请跳转:Linux 网络编程项目--简易ftp-CSDN博客

  • 36
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值