网络编程相关概述
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字节 | 0x01 | 0x04 |
1字节 | 0x02 | 0x03 |
2字节 | 0x03 | 0x02 |
3字节 | 0x04 | 0x01 |
网络字节序的定义
将收到的第一个字节的数据当做高位来看待,这就要求发送端的发送的第一个字节应该是高位。而在发送端发送数据时,发送的第一个字节是该数字在内存中起始地址对应的字节。即多字节数值在发送前,在内存中数值应该以大端法存放。
所以,网络协议指定了通讯字节序:大端,只有在多字节数据处理时才需要考虑字节序。
运行在同一台计算机上的进程相互通信时,一般不用考虑字节序;计算机之间进行通讯时,需要将各自的字节序转换为网络字节序。
字节序转换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撒花!!!!!!!!!!!