Linux网络编程基础

一、TCP/UDP对比

  1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前,不需要建立连接。
  2. TCP提供可靠的服务。也就是说,通过TPC连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
  3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率减低(对实时应用很有用,如IP电话,实时视频会议等)
  4. 每一条TPC连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
  5. TCP首部开销20字节;UDP的首部开销小,只有8个字节。
  6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。

二、端口号作用

        一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等。

        这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP地址与网络服务的关系是一对多的关系。

        实际上是通过“IP地址+端口号”来区分不同的服务的。

        端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。

三、字节序

        字节序,即字节在电脑中存放时的序列与输入(输出)时的序列是先到的在前还是后到的在前。字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。

参见序:

        1. Little endian:将低序字节存储在起始地址(小端字节序)

        2. Big endian:将高序字节存储在起始地址(大端字节序)

网络字节序=大端字节序

例子:在内存中双字0x01020304(DWORD)的存储方式

内存地址

4000&4001&4002&4003

LE 04 03 02 01

BE 01 02 03 04

例子:如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为

big-endianlittle-endian

0x0000 0x12 0xcd

0x0001 0x34 0xab

0x0002 0xab 0x34

0x0003 0xcd 0x12

x86系列CPU都是little-endian的字节序。

字节序转换api:

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

uint16_t htons(uint16_t host16bitvalue);//返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue);//返回网络字节序的值
uint16_t ntohs(uint16_t net16vitvalue); //返回主机字节序的值uint32_t
ntohl(uint32_t net32bitvalue);          //返回主机字节序的值

        h代表host,n代表net,s代表short(两个字节),l代表long (4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取。

四、网络编程的场景

        比如说,我现在是一个客户端,我的面前有五座房子,我要访问这五座房子当中的某一座房子,并且要走到这个房子里面的某一间,这个时候站着这五座房子面前很迷茫,突然第二座房子上面有个人在叫,“我是服务器,是说汉语(TCP/UDP),我的IP地址(楼号)是。。我的端口号(房间号)是。。我在监听(等待大家的来访,来了敲门)”,然后客户端获取服务器IP获取服务器端口连接。

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

1.是说汉语(TCP/UDP):

       int socket(int domain, int type, int protocol);//有点像open函数

domain:指明所使用的协议族,通常为 AF_INET ,表示互联网协议族( TCP/IP 协议族);

  • AF_INET        IPv4 因特网域
  • AF_INET6      IPv6 因特网域
  • AF_UNIX  Unix       域
  • AF_ROUTE            路由器套接字
  • AF_KEY                 密钥套接字
  • AF_UNSPEC          未指定 

 type 参数指定 socket 的类型:

  • SOCK_STREAM:流式套接字提供可靠的、面向连接的通信流;它使用 TCP 协议,从而保证了数据传输的正确性和顺序性。
  • SOCK_DGRAM: 数据报套接字定义了一种无连接的服,数据通过相互独立进行传输,是无序的,并且不保证是可靠、无差错的。它使用的数据报协议 UDP
  • SOCK_RAW:允许程序使用低层协议,原始套接字允许对底层协议如 IPICMP 进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。

 protocol 通常赋值“ 

  • 0 选择 type 类型对应的默认协议
  • IPPROTO_TCP  TCP 传输协议
  • IPPROTO_UDP  UDP  传输协议
  • IPPROTO_SCTP  SCTP 传输协议
  • IPPROTO_TIPC  TIPC  传输协议

2.地址准备好: 我的IP地址(楼号)是。。我的端口号(房间号)是。。

bind()函数:IP号端口号与相应描述字赋值函数。

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

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

 功能

  • 用于绑定 IP 地址和端口号到 socketfd

参数

  • sockfd  :是一个 socket 描述符
  • addr :是一个指向包含有本机 IP 地址及端口号等信息的 sockaddr 类型的指针,指向要绑定给 sockfd 的协议地址结构。这个地址结构根据地址创建 socket 时的地址协议族的不同而不同。 
  • addrlen : 是 sockaddr_in 结构体的空间大小。
//ipv4对应的是:
struct sockaddr {
    sa_family_t sa_family;
    char        sa_data[14];
};
//同等替换
struct sockaddr_in{
    sa_family_t    sin_family;  //协议族
    in_port_t      sin_port;    //端口号
    struct in_addr  sin_addr;   //IP地址结构体
    unsigned char  sin_zero[8]; //填充  没有实际意义,只是为跟sockaddr结构在内存中对齐,这样两者才能相互转换
};

 该函数的查找方式:

CLC@Embed_Learn:~/IPC$ cd /usr/include
CLC@Embed_Learn:/usr/include$ grep "struct sockaddr_in {" * -nir
linux/in.h:184:struct sockaddr_in {
CLC@Embed_Learn:/usr/include$ vi linux/in.h +184

 地址转换API:

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

int inet_aton(const char *straddr, struct in_addr *addrp);
//把字符串形式的“192.168.1.123”转为网络能识别的格式
char *inet_ntoa(struct in_addr inaddr);
//把网络格式的IP地址转为字符串形式

 3.监听:我在监听(等待大家的来访,来了敲门)

listen()函数:监听设置函数

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);

 功能

  • 设置处理的最大连接数,listen() 并未开始接受连线,只是设置 sockect listen 模式,listen 函数只用于服务端,服务器进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字排队的最大连接数。
  • 内核为任何一个给定监听套接字维护两个队列:
    • 未完成连接队列,每个这样的 SYN 报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的 TCP 三次握手过程。这些套接字处于 SYN_REVD 状态;
    • 已完成连接队列,每个已完成 TCP 三次握手过程的客户端对应其中一项。这些套接字处于 ESTABLISHED 状态; 

 参数

  • sockfdsockfd socket 系统调用返回的服务器端 socket 描述符。
  • backlogbacklog 指定在请求队列中允许的最大请求数。

 4.连接:

accept()函数

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

功能

  • accpet 函数由 TCP 服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。 

参数

  • sockfdsockfd socket 系统调用返回的服务器端 socket 描述符。
  • addr:用来返回已连接的对端(客户端)的协议地址。
  • addrled:客户端地址长度。 

返回值

  • 该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数就是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示 TCP 三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。 

5.字节流读取函数 

        在套接字通信中进行字节读取函数:read()write()。与 I/O 中读取函数略有区别,因为它们输入或输出的字节数可能比请求的少。

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
/*说明:函数均返回读或写的字节数,出错则返回-1*/

         第一个将 buf 中的 nbytes 个字节写入到文件描述符 fd 中,成功时返回写的字节数。第二个为从 fd 中读取 nbyte 个字节到 buf 中,返回实际所读的字节数。详细应用说明参数使用 read  write  读写  socket (套接字)。

        网络 I/O 还有一些函数,例如:

recv() / send () , readv() / writev() , recvmsg() / sendmsg() , recvfrom() / sendto()等。

数据收发常用第二套API

在TCP套接字上发送数据函数:有连接

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//包含3要素:套接字s,待发数据msg,数据长度len
//函数只能对处于连接状态的套接字使用,参数s为已建立好连接的套接字描述
//符,即accept函数存放待发送数据的缓冲区
//参数len为待发送数据的长度,参数flags为控制选项,一般设置为0

在TCP套接字上接收数据:有连接

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//包含3要素:套接字s,待发数据buf,数据长度len
//函数recv从参数s所指定的套接字描述符(必须是面向连接的套接字)上接收
//数据并保存到参数buf所指定的缓冲区
//参数len则为缓冲区长度,参数flags为控制选项,一般设置为0

 6.客户端的connect函数

connect()函数:客户机连接主机

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

 功能

  • 该函数用于绑定之后的 client 端(客户端),与服务器建立连接

参数

  • sockfdsockfd socket 系统调用返回的服务器端 socket 描述符。
  • addr:是服务器端的 IP 地址和端口号的地址结构指针。
  • addrlen:地址长度常被设置为 sizeof(struct sockaddr) 

返回值

  • 成功返回 0 ,遇到错误时返回 -1 ,并且 errno 中包含相应的错误码。 

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

Sockt编程服务端编写:

  1.  创建套接字(socket())
  2. 为套接字添加信息(IP地址和端口号)(bind())
  3. 监听网络连接(listen())
  4. 监听到有客户端接入(connect()),接受一个连接(accept())
  5. 数据交互
  6. 关闭套接字,断开连接
#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>
int main(){

        int s_fd;
        int n_read;
        char readbuf[128];
        char *msg = "hello world !";
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;

        memset(&s_addr,0,sizeof(struct sockaddr_in));//对数据进行清空格式化
        memset(&c_addr,0,sizeof(struct sockaddr_in));

        //1.socket
        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(8989);//端口号一般5000以上,低于3000一般是操作系统所使用的,调用htons使端口号变成网络字节序
        inet_aton("192.168.1.106",&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);
        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));
        //5.read
        n_read = read(c_fd, readbuf, 128);
        if(n_read == -1){
                perror("read");
        }else{
                printf("get message:%d,%s\n",n_read,readbuf);
        }
        //6.write
        write(c_fd,msg,strlen(msg));
        return 0;
}

Sockt编程客户端编写:

  1.  创建套接字(socket())
  2. 客户端接入(connect())
  3. 数据交互
  4. 关闭套接字,断开连接
#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>
int main(){

        int c_fd;
        int n_read;
        char readbuf[128];
        char *msg = "msg from client";
        struct sockaddr_in c_addr;

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

        //1.socket
        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(8989);
        inet_aton("192.168.1.106",&c_addr.sin_addr);

        //2.connect  传递参数和bind差不多
        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
                perror("connect");
                exit(-1);
        }
        //3.send
        write(c_fd,msg,strlen(msg));
        //4.read
        n_read = read(c_fd, readbuf, 128);
        if(n_read == -1){
                perror("read");
        }else{
                printf("get message form server:%d,%s\n",n_read,readbuf);
        }

        return 0;
}

六、实现双方聊天

Sockt编程服务端编写:

#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;
        int s_fd;
        int n_read;
        char readbuf[128];
        char msg[128] = {0};
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;

        if(argc != 3){
                printf("param is not good\n");
                exit(-1);
        }

        memset(&s_addr,0,sizeof(struct sockaddr_in));//数据清空
        memset(&c_addr,0,sizeof(struct sockaddr_in));

        //1.socket
        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]));
        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){
                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));//清空msg数据
                                        printf("input: ");
                                        gets(msg);
                                        write(c_fd,msg,strlen(msg));
                                }
                        }
                        //5.read
                        while(1){
                                memset(readbuf,0,sizeof(readbuf));//清空readbuf数据
                                n_read = read(c_fd, readbuf, 128);
                                if(n_read == -1){
                                        perror("read");
                                }else{
                                        printf("get message:%d,%s\n",n_read,readbuf);
                                }
                        }
                       //6.write
                        break;
                }
        }
        return 0;
}

Sockt编程客户端编写:

#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>
int main(int argc,char **argv){

        int c_fd;
        int n_read;
        char readbuf[128];
        char msg[128] = {0};
        struct sockaddr_in c_addr;
        if(argc != 3){
                printf("param is not good\n");
                exit(-1);
        }

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

        //1.socket
        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(atoi(argv[2]));
        inet_aton(argv[1],&c_addr.sin_addr);

        //2.connect
        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
                perror("connect");
                exit(-1);
        }
        while(1){
                if(fork() == 0){
                        while(1){
                                memset(msg,0,sizeof(msg));
                                printf("Input: ");
                                //3.send
                                gets(msg);
                                write(c_fd,msg,strlen(msg));
                        }
                }
                        while(1){
                                memset(readbuf,0,sizeof(readbuf));
                                //4.read
                                n_read = read(c_fd, readbuf, 128);
                                if(n_read == -1){
                                        perror("read");
                                }else{
                                        printf("get message form server:%d,%s\n",n_read,readbuf);
                                }
                        }

        }
        return 0;
}

编写完之后发现里面有些whle(1)循环没必要出现,修改后为

Sockt编程服务端编写:

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

int main(int argc,char **argv){
//      int socket(int domain, int type, int protocol);
        int s_fd;
        int c_fd;
        int n_read;

        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;
        if(argc != 3){
                printf("param is not good\n");
                exit(-1);
        }
        char readbuf[128] = {0};
        char msg[128] = {0};

        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);
        }

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

        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1],&s_addr.sin_addr);

        bind(s_fd,(struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
        listen(s_fd, 10);
        int clen = sizeof(struct sockaddr_in);

        c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);
        if(c_fd == -1){
                perror("socket");
        }
        printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));

        if(fork() == 0){
                while(1){
                        memset(msg, 0, sizeof(msg));
                        printf("Input:");
                        gets(msg);
                        write(c_fd, msg,strlen(msg));
                }
        }
                while(1){
                        memset(readbuf, 0, sizeof(readbuf));
                        n_read = read(c_fd, readbuf, 128);
                        if(n_read == -1){
                                perror("read");
                        }else{
                                printf("get message:%d,%s\n",n_read,readbuf);
                        }
                }

        return 0;
}

Sockt编程客户端编写:

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

int main(int argc,char **argv){
//      int socket(int domain, int type, int protocol);
        int c_fd;

        struct sockaddr_in c_addr;

        char readbuf[128] = {0};
        char msg[128] = {0};
        if(argc != 3){
                printf("param is not good\n");
                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);
        }

//      int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen); 
        c_addr.sin_family = AF_INET;
        c_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1],&c_addr.sin_addr);

        if(connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr)) == -1){
                perror("connect");
                exit(-1);
        }
        if(fork() == 0){
                while(1){
                        memset(msg, 0, sizeof(msg));
                        printf("Input:");
                        gets(msg);
                        write(c_fd, msg, strlen(msg));
                }
        }
        while(1){
                memset(readbuf, 0, sizeof(readbuf));
                int n_read = read(c_fd, readbuf, 128);
                if(n_read == -1){
                        perror("read");
                }else{
                        printf("get message from server:%d,%s\n",n_read,readbuf);
                }
        }
        return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从入门到捕蛇者说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值