Linux系统编程整理之二(网络编程-快速入门篇)

网络编程相关概述

IPC的特点:依赖于内核,无法做到网络间通信(应用层的两个进程间的通信)

网络编程的本质目的:实现多机通讯

Socket编程

网络编程主要是使用Socket编程,在UNIX、Linux系统中一切皆文件(为了统一对各种硬件的操作,简化接口,不同的硬件设备都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作)
为了表示和区分已经打开的文件,UNIX/Linux会为每个文件分配一个id,这个文件就是一个整数,即文件描述符
通常用 0 来表示标准输入文件(stdin),它对应的硬件设备就是键盘;
通常用 1 来表示标准输出文件(stdout),它对应的硬件设备就是显示器。
网络连接也是一个文件,它也有文件描述符
可以通过 socket() 函数来创建一个网络连接,(打开一个网络文件),socket() 的返回值就是文件描述符(在windows下的socket返回的叫文件句柄(文件控制块))。

利用文件描述符,即可使用普通的文件操作函数来传输数据
read() 读取从远程计算机传来的数据;write() 向远程计算机写入数据。

网络编程基本要素

协议

计算机网络通信制定的规则(数据 :数据格式),比如 http、TCP、UDP;
在OSI七层网络模型中,各个层之间都有相对应的协议,网络编程主要关注的是传输层的协议(TCP/UDP协议);标准套接字分为TCP和UDP协议两种不同type的工作流程,TCP网络编程相对于UDP来说复杂一些。

TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输(类似于建立通信:打电话)。在发送数据的准备阶段,客户端与服务器之间的三次交互(建立连接的三次握手),确保连接的可靠。

(1)第一次握手:Client首先向Server发送连接请求报文段,报文段的首部中的标志位SYN置为1,同步自己的seq(指明自己的初始化序号seq=x),Client进入SYN_SENT状态。
(2)第二次握手:Server收到Client的连接请求报文段,返回给Client自己的应答报文,SYN被置为1并且ack=x+1以及(服务器选择自己的初始序号seq=y),Server进入SYN_REVD状态。
(3)第三次握手:Client收到Server的SYN-ACK报文后,再次向服务器发送ACK报文段,该报文段中序号seq=x+1,确认号ack=y+1;这个报文段已经可以携带数据了。Client进入ESTABLISHED状态。
服务器再次收到 ACK 报文之后,也进入 ESTABLISHED 状态,此时,双方以建立起了连接

(第一次握手和第二次握手都只是消耗掉一个序号,但不能携带数据;第三次握手可以携带数据)

断开连接的四次挥手

(1)第一次挥手:Client向Server发送断开连接请求的报文段,报文段中指定序号:seq=u(u为Client最后一次向Server发送报文段的最后一个字节序号加1),Client进入FIN-WAIT-1状态。
(2)第二次挥手:Server收到断开报文段后,立即向Client发送确认报文段,seq=v(v为Server最后一次向Client发送报文段的最后一个字节序号加1),ack=u+1,Server进入CLOSE-WAIT状态。此时这个TCP连接处于半关闭状态,Server发送数据的话,Client仍然可以接收到。
(3)第三次挥手:Server向Client发送断开确认报文段,seq=w((重新指定一个序号),w为半关闭状态下Server最后一次向Client发送报文段的最后一个字节序号加1),ack=u+1,Server进入LAST-ACK状态。
(4)第四次挥手:Client收到Server的断开确认报文段后,再向Server发送确认断开报文段,(上一次Client发送的报文段序号是u)则此次为seq=u+1,ack=w+1,Client进入TIME-WAIT状态。需要经过一段时间确保Server收到Client的确认断开报文(ACK报文),进入CLOSED状态,断开了TCP连接。服务器收到ACK报文后,就关闭连接,也处于CLOSED状态了。

 (Client在TIME-WAIT状态等待时间为2*MSL((Maximum Segment Life)),确认Client向Server发送的最后一次断开确认到达(如果没有到达,Server会重发(第三次挥手)中的断开确认报文段给Client,告诉Client你的最后一次确认断开没有收到)。经过一段时间确保:如果Client在TIME-WAIT过程中没有再次收到Server的(第三次挥手)报文段,就进入CLOSED状态。TCP连接至此断开)

UDP:用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。(类似于面向报文:发短信),每个数据包的大小限制在64k以内。因为无连接,即是不可靠协议,但是传输速度快(容易丢失数据),数据量大、内存响应速度快。

(视频帧交互(数据交互,如视频卡顿或者视频出现某片小雪花(丢失几个bit))影响不会太大、QQ聊天平台等处常用)

TCP/UDP 对比

1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前       不需  要建立连接

2.  TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

3.  TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文流

UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

4.  每一条TCP连接只能是点到点的;UDP支持一对一一对多多对一多对多的交互通信

5.  TCP首部开销20字节;UDP的首部开销小,只有8个字节

6.  TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

(开发经验:socket套接字主要为 TCP(可靠),支持精细操作(控制))

地址

 网络编程的地址由IP地址端口号组成!!!!!!!!

IP地址:互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。IP分为(IPV4)和(IPV6)。

(IP地址类似于楼号(IP地址可以唯一标识网络中的设备),每一个数据包都必须携带目的地址IP和源IP地址,路由器依靠此信息为数据包选择最优路由(路线),IP地址提供网络服务: FTP服务、HTTP(Web服务)、socket、SMTP服务等)

 端口号:端口号可以唯一标识设备中的进程(运行的应用程序)。(TCP和UDP的端口号是相互独立的)端口号取值范围是0到65535(两个字节表示的整数)。其中0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。(端口号类似于楼里的房号)

端口号作用

一台拥有IP地址的主机可以提供许多服务,这些服务完全可以通过1个IP地址来实现。为了区分不同的网络服务,显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。

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

(普通用户 5000到9000的端口号用的比较多,低于3000一般是操作系统使用的关键端口)

查看端口号的占用状态,实际开发过程中如果连接不上服务器,可能服务器端口被占用,此时可以更换一个或杀死占用的进程。

netstat -ntulp | grep 8888 #查看所有8888端口使用情况,最后一栏是PID/进程名称
ps -aux | grep pid_name
ps -aux | grep pid

#查看,即可明确知道8888是被哪个程序占用了!然后判断是否使用kill命令

利用(协议 + IP地址 + 端口号)标识,即可标识网络中的进程,网络通信就可以利用这个标识进行交互。

字节序

 网络字节序为大端字节序(Big endian)(大端字节序更符合人类习惯);计算机电路先处理低位字节,效率比较高(计算就是从低位开始的(起始地址),因此计算机内部很多都是小端字节序)

小端格式(Little-Endian):将低位字节数据存储在低地址

大端格式(Big-Endian):将高位字节数据存储在低地址

内存中的双字(DWORD)数据0x01020304 在不同机器中的存储:

序(地址由低到高递增)\字节序格式大端小端
0字节0x010x04
1字节0x020x03
2字节0x030x02
3字节0x040x01
网络字节序的定义

将收到的第一个字节的数据当做高位来看待,这就要求发送端的发送的第一个字节应该是高位。而在发送端发送数据时,发送的第一个字节是该数字在内存中起始地址对应的字节。即多字节数值在发送前,在内存中数值应该以大端法存放。

所以,网络协议指定了通讯字节序:大端,只有在多字节数据处理时才需要考虑字节序。

运行在同一台计算机上的进程相互通信时,一般不用考虑字节序;计算机之间进行通讯时,需要将各自的字节序转换为网络字节序。

字节序转换API
#define uint16_t unsigned short int
#define uint32_t unsigned long
#include <netinet/in.h>

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

(h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY(服务器端地址选择,是一个常量,它指代的是一个特殊的IP地址,即0.0.0.0),INADDR_ANY指定地址让操作系统自己获取)(这个参数就表明可以连接到server的所有ip都是可以的,极大的简化了需要创建socket的数量)

/*
    就绑定一个INADDR_ANY和一个端口,
    然后客户端通信到server的所有ip都用这个socket来处理

*/


// tcpclient.c
struct sockaddr_in servaddr;
 
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(12345);
inet_pton(AF_INET,argv[1],&seraddr.sin_addr);

// tcpserver.c
struct sockaddr_in servaddr;
 
servaddr.sin_family  = AF_INET;
servaddr.sin_port = htons(12345);
servaddr.sin_address.s_addr = htonl(INADDR_ANY);
./tcpclient 0.0.0.0   #可以成功通信

socket服务器和客户端的开发

TCP通信流程

服务器端:

1.创建套接字(类似open()作用)(socket)
2.为套接字添加信息;将socket与IP地址端口号绑定(bind)
3.监听网络连接;监听被绑定的端口(listen)
4.接收连接请求;监听到有客户端接入,接受一个连接(accept)
5.数据交互 从socket中读取客户端发送来的信息(read)向socket中写入信息(write)
6.关闭套接字,断开连接;关闭socket(close)

客户端:

1.创建套接字(socket)
2.连接指定计算机的端口(connect)
3.向socket中写入信息(write)
4.从socket中读取服务端发送过来的消息(read)
5.关闭socket(close)

具体过程如图:

(图片有点小瑕疵;个人理解:connect()应该箭头到listen() 和 accept()之间)

socket编程相关函数(TCP)

socket()函数(创建套接字)

#include <sys/types.h>          
#include <sys/socket.h>

/*
    用于创建套接字,同时指定协议和类型
    返回值:成功返回非负套接字描述符,失败返回-1
*/

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

bind()函数(套接字绑定)

函数作用:用于绑定IP地址和端口号到socket

/*
    addrlen:地址的长度,一般用sizeof(struct sockaddr_in)表示
    返回值:成功返回0,失败返回-1
*/

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

/*
  sockaddr在<sys/socket.h>中定义,
  sockaddr的缺陷是:sa_data把IP地址和端口号信息混在一起了,使用的少

  sockaddr_in在<netinet/in.h>或<arpa/inet.h>中定义,
  该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中
*/


// Internet address 32位网络地址
struct in_addr {
    __be32  s_addr;
};


struct sockaddr {  
     sa_family_t sin_family;//地址族
    char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
   }; 


struct sockaddr_in {
  __kernel_sa_family_t  sin_family; /* 地址族Address family       */
  __be16        sin_port;   /* 16位端口号Port number          */
  struct in_addr    sin_addr;   /* 网络地址Internet address     */

};

sockaddr_in 和 sockaddr 两者结构体长度相同,即占用大小相等的内存,16个字节;二者可以相互转化,为并列结构,指向sockaddr_in结构的指针也可以指向sockaddr结构。(开发经验:一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket信息的定义和赋值;sockaddr用于函数参数。)


地址转换API
inet_aton()函数
/*
    函数将点分十进制字符串转换成32位无符号整数(只适用于IPV4地址)
    如 把字符串形式的 “192.168.10.11” 转为网络能识别的格式

    参数:
        cp:将要转换的点分十进制IP地址
        inp:保存被转换完成的二进制的IP地址

    返回值:
        地址合法,则返回非0值,否则返回0值
*/

int inet_aton(const char *cp, struct in_addr *inp);

inet_ntoa()函数
/*
    函数用来将参数in所指的大端网络字节序二进制的数字转换成ipv4点分十进制
    字符串网络地址,然后将指向此网络地址字符串的指针返回。
    (把网络格式的ip地址转化为字符串形式)

    参数:
        in:将被转换的二进制IP地址(即32位无符号整数)

    返回值:
        成功:返回点分十进制的IP地址 (字符串指针), 失败:返回NULL
*/

char *inet_ntoa(struct in_addr in);
函数拓展

inet_pton()函数

/*
    函数将字符串src转换为af地址类型协议簇的网络地址,并存储到dst中。
    (适用于IPV4和IPV6地址)

    参数:
        af: AF_INET或AF_INET6
        src:将要转换的点分十进制的IP地址
        dst:保存被转换完成的二进制IP地址

    返回值:
    成功:则返回1 失败:返回 0
    (如果指定的地址类型协议簇不合法,返回-1,并且errno设置为EAFNOSUPPORT)
*/

int inet_pton(int af, const char *src, void *dst);

inet_ntop()函数

/*
    该函数将地址类型协议簇为af的网络地址src转换为字符串,并将其存储到dst中;

    (inet_ntop拓展自inet_ntoa来支持多种地址类型协议簇,将二进制的IP地址转换成点分十进制的字符串(IP地址),inet_ntoa现在基本被弃用)

    参数:
        af:AF_INET或AF_INET6
        src:二进制的IP地址
        dst:保存被转换成点分十进制的IP地址
        size:指定可使用的缓冲字节数

    返回值:
        成功:返回一个指向dst的非空指针,失败:返回NULL,并且errno设置为相应的错误类型
*/

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

(开发经验:dst不能是空指针,在参数size中指定可使用的缓冲字节数)

listen()函数(监听端口)

函数作用:监听客户端的接入

/*
    返回值:成功返回0,失败返回-1
*/

int listen(int sockfd, int backlog);

accept()函数(接收连接请求)

/*
    返回值:一个新的套接字的描述符,表示已连接的套接字描述符
    而第一个参数是服务器监听套接字描述符,一个服务器通常仅仅创建一个监听套接字,
    它在该服务器的生命周期内一直存在
*/

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

(如果不关心客户端地址,可以对随后两个参数传NULL值)

connect()函数(发送连接请求)

/*
    返回值:成功返回0,遇到错误时返回-1,并且errno中包含相应的错误码

    参数:
    sockfd:创建的socket描述符
    addr:服务端的ip地址和端口号的地址结构指针
    addrlen:地址的长度,通常被设置为sizeof(struct sockaddr)

*/

//用于绑定之后的client端(客户端),与服务器建立连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);



//通过遇到错误的选择分支打印错误信息
if(connect(client_fd, (struct sockaddr *)&client_addr,sizeof(struct sockaddr)) == -1){
		perror("connect");
		exit(-1);
	}

TCP的数据收发

recv()函数
/*
    函数接收套接字中的数据

    参数:
    sockfd:在哪个套接字接
    buf:存放要接收的数据的首地址
    len:要接收的数据的字节
    flags:设置为MSG_DONTWAITMSG 时 表示非阻塞,设置为0时 功能和read一样

    返回值:成功:返回实际发送的字节数,失败:返回 -1
*/


ssize_t recv(int sockfd, const void *buf, size_t len, int flags);  
send()函数
/*
    函数只能对处于连接状态的套接字进行使用,参数sockfd为已建立好连接的套接字描述符

    参数:
    sockfd:为已建立好连接的套接字描述符即accept函数的返回值
    buf:要发送的内容
    len:发送内容的长度
    flags:设置为MSG_DONTWAITMSG 时 表示非阻塞,设置为0时 功能和write一样

    返回值:成功:返回实际发送的字节数,失败:返回 -1
*/


ssize_t send(int sockfd, const void *buf, size_t len, int flags);

函数拓展

UDP通信流程

服务器端:

1.获得套接字文件描述符 ( socket );

2.绑定监听端口,绑定套接字文件描述符和地址类型变量(通过struct sockaddr_in 结构设置服务器地址监听端口) ( bind );

3.接收客户端的数据 ( recvfrom );

4.向客户端发送数据,也向服务器主机发送数据 ( sendto );

5.关闭套接字,释放资源 ( close );

客户端:

1.获得套接字文件描述符 ( socket );

2.向服务器发送数据 (通过struct sockaddr_in 结构设置服务器地址和监听端口)( sendto );

3.接收服务器的数据 ( recvfrom ) ;

4.关闭套接字 ( close ) ;

具体过程如图:

socket编程相关函数(补充UDP)

UDP的数据收发

recvfrom()函数
/*
    函数用于接收消息

    参数:
        s:      socket描述符;
        buf:    UDP数据报缓存区(包含所接收的数据);
        len:    缓冲区长度;
        flags:  调用操作方式(一般设置为0),设置为MSG_DONTWAITMSG 时 表示非阻塞;
        from:   指向发送数据的客户端地址信息的结构体(sockaddr_in需类型转换);
        fromlen:指向from结构体长度值;

    返回值:成功: 则返回实际接收到的字符数,失败返回-1,错误原因存在 errno 中。
*/

int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, \
                int *fromlen); 

sendto()函数
/*
    函数用于发送消息

    参数:
        s:     socket描述符;
        buf:   UDP数据报缓存区(包含待发送数据);
        len:   UDP数据报的长度;
        flags: 调用方式标志位(一般设置为0),设置为MSG_DONTWAITMSG 时 表示非阻塞;
        to:    指向接收数据的主机地址信息的结构体(sockaddr_in需类型转换);
        tolen: to所指结构体的长度;

    返回值:成功:则返回实际传送出去的字符数,失败返回-1,错误原因存在errno 中。
*/

int sendto(int s, const void *buf, int len, unsigned int flags, \
            const struct sockaddr *to, int tolen);

网络编程多机通信案例

双机聊天

Version1

使用主函数传参数配置IP和端口,使用fork()创建子进程,并且用循环控制,避免主进程在接收完一次信息后结束,不断接收(进行多次信息交互);客户端发送一次后结束进程,服务器端不断循环不结束主进程。

服务器端(server.c)

#include <stdio.h>
#include <sys/types.h>      
#include <sys/socket.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 s_fd;
        int c_fd;
        int n_read;
        char readBuf[128];
 
        //char *msg = "Accept the message";   //静态写入
        char msg[128] = {0};                  //动态写入

        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));
 
        if(argc != 3){              //判断传入main中的参数个数,辅助段错误的Debug。
                printf("Param isn't right\n");
                exit(-1);
        }
 
        //1.socket
        s_fd = socket(AF_INET, SOCK_STREAM, 0);
        if(s_fd == -1){
                perror("socket");
                exit(-1);
        }
 
        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(atoi(argv[2]));
        //s_addr.sin_addr.s_addr = "192.0.0.1";  (应为__be32类型的s_addr 出错)
        inet_aton(argv[1], &s_addr.sin_addr);
 
        //2.bind
        bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
 
        //3.listen
        listen(s_fd, 10);
 
        //4.accept
        int client_len = sizeof(struct sockaddr_in);
 
        while(1){
                c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &client_len);
                if(c_fd == -1){
                        perror("accept");
                }
                printf("Connect The client IP: %s\n",inet_ntoa(c_addr.sin_addr));
 
                if(fork() == 0){

                        //5.read
                        n_read = read(c_fd, readBuf, 128);
 
                        if(n_read == -1){
                                perror("read");
                        }else{
                                printf("Get Message: %d len_size, %s\n",n_read, readBuf);
                        }
 
                        //6.write
                        //write(c_fd, msg, strlen(msg));

                        //6.write (动态写入)
                        memset(msg, 0, sizeof(msg));
                        printf("Input: ");
                        gets(msg);
                        write(c_fd, msg, strlen(msg));
 
                        break;    //子进程执行一次退出,父进程循环接收数据
                }
        }

        return 0;
}

客户端(client.c)

#include <stdio.h>
#include <sys/types.h>      
#include <sys/socket.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 c_fd;
        int n_read;
        char readBuf[128];
 
        char *msg = "The static data from client";
        struct sockaddr_in c_addr;
 
        memset(&c_addr, 0, sizeof(struct sockaddr_in));

        int n_write;
 
        if(argc != 3){                           
                printf("Param isn't right\n");
                exit(-1);
        }
 
        //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_in)) == -1){
                perror("connect");
                exit(-1);
        }
 
        //3.send
        n_write = write(c_fd, msg, strlen(msg));
        if(n_write == -1){
                perror("Write ");
        }
 
        //4.read
        n_read = read(c_fd, readBuf, 128);
        if(n_read == -1){
                perror("Read ");
        }else{
                printf("Get the Message from server: %d len_size, %s\n",n_read, readBuf);
        }


        return 0;
}

Version2

客户端与服务器端不断进行通信;  (执行两个while死循环,要么多线程处理,要么创建子进程处理)

服务器端(server.c)

#include <stdio.h>
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
int main(int argc, char const *argv[])
{
        int s_fd;
        int c_fd;
        int n_read;
        char readBuf[128];
 
        //char *msg = "Accept the message";   //静态写入
        char msg[128] = {0};                  //动态写入

        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));
 
        if(argc != 3){              //判断传入main中的参数个数,辅助段错误的Debug。
                printf("Param isn't right\n");
                exit(-1);
        }
 
        //1.socket
        s_fd = socket(AF_INET, SOCK_STREAM, 0);
        if(s_fd == -1){
                perror("socket");
                exit(-1);
        }
 
        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1], &s_addr.sin_addr);
 
        //2.bind
        bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
 
        //3.listen
        listen(s_fd, 10);
 
        //4.accept
        int client_len = sizeof(struct sockaddr_in);
 
        while(1){
                c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &client_len);
                if(c_fd == -1){
                        perror("accept");
                        exit(-1);
                }
                printf("Connect Successfully, The client IP: %s\n",inet_ntoa(c_addr.sin_addr));
 
                if(fork() == 0){
                        if(fork() == 0){
                            while(1){
                                //6.write (不断写入)
                                memset(msg, 0, sizeof(msg));
                                printf("Input: ");
                                gets(msg);
                                write(c_fd, msg, strlen(msg));
                            }
                        }
             
                        while(1){
                             //5.read (不断读取)
                             memset(readBuf, 0, sizeof(readBuf));
                             n_read = read(c_fd, readBuf, 128);
                             if(n_read == -1){
                                perror("Read ");
                             }else{
                                printf("Get message: %d byte, %s\n",n_read, readBuf);
                             }  

                        }
                
                        //break; 
                }
        }
        return 0;
}

客户端(client.c)

#include <stdio.h>
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
int main(int argc, char const *argv[])
{
        int c_fd;
        int n_read;
        char readBuf[128];
 
        //char *msg = "The static data from client";
        char msg[128] ={0};

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

        int n_write;
        char *exit_string= "exit";
 
        if(argc != 3){                           
                printf("Param isn't right\n");
                exit(-1);
        }
 
        //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_in)) == -1){
                perror("connect");
                exit(-1);
        }

        while(1){
                if(fork() == 0){
                      while(1){
                        //3.send,写:用子进程写
                        memset(msg, 0, sizeof(msg));
                        printf("Input: ");
                        gets(msg);   // 不写入时,阻塞
                        n_write = write(c_fd, msg, strlen(msg));
                        if(n_write == -1){
                              perror("Write ");
                        }
                        if(strcmp(exit_string, msg) == 0){   //输入exit 客户端退出
					          exit(0);
				        }
                      }  
                }

                while(1){
                     //4.read,读:阻塞
                     memset(readBuf, 0, sizeof(readBuf));
                     n_read = read(c_fd, readBuf, 128);
                     if(n_read == -1){
                         perror("Read ");
                     }else{
                         printf("Get the Message from server: %d byte, %s\n",n_read, readBuf);
                     }
                }

            close(c_fd);
        }

        return 0;
}

多机通信

在双机聊天实验中,解决资源竞争问题。(服务器端以心跳包格式写入,回复各自的客户端进行通信)

服务器端(server.c)

#include <stdio.h>
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
int main(int argc, const char *argv[])
{
        int s_fd;
        int c_fd;
        int n_read;
        char readBuf[128];
 
        //char *msg = "Accept the message";   //静态写入
        char msg[128] = {0};                  //动态写入

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

        int mark = 0; //使用掩码标志位
 
        if(argc != 3){              //判断传入main中的参数个数,辅助段错误的Debug。
                printf("Param isn't right\n");
                exit(-1);
        }
 
        //1.socket
        s_fd = socket(AF_INET, SOCK_STREAM, 0);
        if(s_fd == -1){
                perror("socket");
                exit(-1);
        }
 
        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1], &s_addr.sin_addr);
 
        //2.bind
        bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
 
        //3.listen
        listen(s_fd, 10);
 
        //4.accept
        int client_len = sizeof(struct sockaddr_in);
 
        while(1){
                c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &client_len);
                if(c_fd == -1){
                        perror("accept");
                        exit(-1);
                }

                mark++;
                printf("Connect successfully, The client IP: %s\n",inet_ntoa(c_addr.sin_addr));
 
                if(fork() == 0){
                        if(fork() == 0){
                            while(1){
                                //6.write (不断写入)
                                sprintf(msg, "N0.%d client connect !", mark);
                               
                                write(c_fd, msg, strlen(msg));
                                sleep(11);
                            }
                        }
             
                        while(1){
                             //5.read (不断读取)
                             memset(readBuf, 0, sizeof(readBuf));
                             n_read = read(c_fd, readBuf, 128);
                             if(n_read == -1){
                                perror("Read ");
                             }else{
                                printf("Get NO.%d message: %d Byte, %s\n",mark, n_read, readBuf);
                             }  

                        }
                       
                        //break; 
                }
        }
        return 0;
}

客户端(client.c)

#include <stdio.h>
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
int main(int argc, const char *argv[])
{
        int c_fd;
        int n_read;
        char readBuf[128];
 
        //char *msg = "The static data from client";
        char msg[128] ={0};

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

        int n_write;
        char *exit_string= "exit";
 
        if(argc != 3){                           
                printf("Param isn't right\n");
                exit(-1);
        }
 
        //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_in)) == -1){
                perror("connect");
                exit(-1);
        }

        while(1){
                if(fork() == 0){
                      while(1){
                        //3.send,写:用子进程写
                        memset(msg, 0, sizeof(msg));
                        printf("Input: ");
                        gets(msg);   // 不写入时,阻塞
                        n_write = write(c_fd, msg, strlen(msg));
                        if(n_write == -1){
                              perror("Write ");
                        }
                        if(strcmp(exit_string, msg) == 0){   //输入exit 客户端退出
					          exit(0);
				        }
                      }  
                }

                while(1){
                     //4.read,读:阻塞
                     memset(readBuf, 0, sizeof(readBuf));
                     n_read = read(c_fd, readBuf, 128);
                     if(n_read == -1){
                         perror("Read ");
                     }else{
                         printf("Get the message from server: %d Byte, %s\n",n_read, readBuf);
                     }
                }

            close(c_fd);
        }

        return 0;
}

Ending撒花!!!!!!!!!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值