引入:
进程间的通信 - 特点: 依赖 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_UDPIPPROTO_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需要的structs_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连接需要的structc_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需要的structs_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 需要的structc_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博客