为什么使用网络编程
在进程通信的时候我们知道,无论是管道、共享内存、消息队列、还是信号都是属于单机之间的通信,即运行在同一台设备上的进程之间的通信,他们无法实现运行在不同设备上的进程之间的通信,为了实现这种不同设备之间进程的通信,即多机通信,就要使用网络编程来实现
网络编程一些概念
两个不同的设备上的进程之间要通信,需要知道对方的IP地址和端口号,例如当一个进程根据IP地址找到对应的主机的时候,这时就需要根据端口号去找到对方进程具体的所在位置,就像你找人根据地址找到了他居住的那栋楼,那这时你需要根据门牌号去找寻到他具体住的是那一间房子
在进行网络通信的时候,双方要约定统一的通信协议,如TCP、UDP协议,它们的对比如下:
当我们在进行通信的准备的时候,需要将IP地址和端口号从主机字节序转换为网络字节序,IP地址也需要转换为网络呢个识别的形式
使用socket实现网络通信
socket服务端API:
创建socket服务端
绑定连接信息
监听客户端
连接客户端
数据收发的API
数据收发的API都是成套使用的,不能混用
第一套
第二套
客户端的API
创建socket客户端
客户端连接服务端
利用socket实现双方聊天的小实例
服务端
server.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
int main(int argc,char *argv[])
{
int s_fd;
int c_fd;
int n_read;
char readBuf[128] = {0};//接受数据的buf
char msg[128] = {0};//发送数据的buf
int mark = 0;
struct sockaddr_in s_addr;//存放服务端socket连接信息的结构体
struct sockaddr_in c_addr;//用来存放接入客户端信息的结构体
memset(&s_addr,0,sizeof(struct sockaddr_in));//清空结构体中的内容
memset(&c_addr,0,sizeof(struct sockaddr_in));
if(argc != 3){//判断用户给的参数全不全,参数不够则退出程序
printf("param deficiency!\n");
exit(-1);
}
//1.socket
s_fd = socket(AF_INET,SOCK_STREAM,0);// 用套接字创建一个支持TCP协议的连接 ,返回一个socket描述符
if(s_fd == -1){//判断套接字是否创建成功,失败则输出原因以及退出程序
perror("socket");
exit(-1);
}
//2.bind
s_addr.sin_family = AF_INET;//采用的通信协议族为IPV4
s_addr.sin_port = htons(atoi(argv[2]));//将Linux的小端字节序端口号转换为网络字节序的端口号赋给结构体的端口变量
inet_aton(argv[1],&(s_addr.sin_addr));//将IP地址转换为网络能识别的形式赋给IP地址变量
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//将上面初始化好的信息绑定到刚刚创建的socket连接上
//3.listen
listen(s_fd,10);//监听客户端,同时设置最多可以接入的客户端数量
//4.accept
socklen_t s_len = sizeof(struct sockaddr_in);
while(1){//死循环不断接受新客户端的接入
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&s_len);//接受客户端接入
if(c_fd == -1){//客户端接入失败打印错误信息
perror("accept");
}
mark++;//定义一个变量方便查看是第几个客户端接入了
printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr)); //打印接入的客户端的ip地址
if(fork() == 0){//每接入一个新客户端就创建一个子进程去服务它
if(fork() == 0){
while(1){//子子进程不断去向客户端发送消息
memset(msg,0,sizeof(msg));
sprintf(msg,"welcome NO.%d client",mark);
//6.write
write(c_fd,msg,strlen(msg));
sleep(3);
}
}
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,%s\n",n_read,readBuf);
}
}
break;
}
}
return 0;
}
客户端
clent.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
int main(int argc,char *argv[])
{
int c_fd;
int n_read;
char readBuf[128] = {0};//接收消息buf
char msg[128] = {0};//发送消息buf
struct sockaddr_in c_addr;//存放socket连接消息的结构体
memset(&c_addr,0,sizeof(struct sockaddr_in));
if(argc != 3){
printf("param definiency!\n");
exit(-1);
}
//1.socket
c_fd = socket(AF_INET,SOCK_STREAM,0);// 用套接字创建一个支持TCP协议的连接 ,返回一个socket描述符
if(c_fd == -1){
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;//采用的通信协议族为IPV4
c_addr.sin_port = htons(atoi(argv[2]));//将Linux的小端字节序端口号转换为网络字节序的端口号赋给结构体的端口变量
inet_aton(argv[1],&(c_addr.sin_addr));//将IP地址转换为网络能识别的形式赋给IP地址变量
//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){
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);
//3.send
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 from server: %d,%s\n",n_read,readBuf);
}
}
break;
}
return 0;
}