目录
socket套接字网络通信学习
数据协议
socket是网络通信,通信的数据协议有http、tcp、udp等等,简单来说就是传输数据的格式,常用的是tcp和udp
tcp
- 简单来说就是两个人进行打电话,a和b打电话的意思,要别人接通才可以立刻收到消息进行交流,所以是可靠的,多用在精准控制,要保证传输数据的准确性,所以tcp连接传数据无差错,不丢失,不重复,且有序
- tcp是面向字节流,实际上是tcp把数据看成一连串无结构的字节流,是全双工的可靠信道
- tcp只支持一对一通信
- tcp首部开销20字节
udp
- 简单来说就是两个人进行发短信,a给b发短信的意思,a不知道b有没有收到,可以不用和b建立通道才发送数据,所以是不可靠的,但udp也不是没有用,因为你要发送视频或者比较大的数据,就可以用到udp,可能中途会有些丢包,但大部分是完整的,视频出现一些小黑点我们也看不见
- udp是面向报文,udp没有堵塞拥挤,对网络视频那些会有好处,是不可靠信道
- udp支持一对一,一对多,多对一,多对多通信
- udp首部开销8个字节
要网络通信,那少不了的就是IP地址和端口号,ip地址是你与谁的机子通信,端口是指机子里的哪个服务通信,一台机子有很多端口
ip地址
- ip地址是你的电脑连上wifi,就会分到一个属于自己的ip地址,属于同一个网络下,就可以与另一个ip地址进行通信
端口
- 端口简单来说就是服务,一台电脑有很多服务,比如web、ftp、smtp等等,
- 别人来找你通信,你ip只有一个,网页又需要通信,vx也要通信,别人发来的数据谁来接收呢,所以一台电脑会分配许多端口来给每个需要通信的服务进行交流,
- 简单来说银行就是ip地址,你进去存钱,你就要进到银行里面的某个窗口办理
- 实际上是通过 “ IP地址 + 端口号 ” 来区分服务的
- 端口提供了一种访问通道,服务器一般是通过知名的端口号来识别,一般某个服务就指定了某个端口,例如ftp服务是21号端口,telnet服务器的端口是23,tftp服务器的端口是69
字节序
字节序是指多字节数据在计算机内存存储或者网络传输时各个字节的存储顺序
- LIttle endian 小端字节序
- Big endian 大端字节序 (网络字节序)
例如存储0x01 0x02 0x03 0x04这四个数据到为 2000 2001 2002 2003这四个地址里
- 小端存储是 2003--0x01 2002--0x02 2001--0x03 2000--0x04
- 大端存储是 2000--0x01 2001--0x02 2002--0x03 2003--0x04
我们将0x1234abcd写入到以0x0000开始的内存中
- Big endian LIttle endian
- 0000 0x12 0xcd
- 0001 0x34 0xab
- 0002 0xab 0x34
- 0003 0xcd 0x12
步骤
我们只需要调用socket的api进行通信就可以,api会调用内核帮我们实现
- tcp server步骤
- 先建立socket套接字,返回网络描述符,后面会用到
- 进行bind连接,在用之前要为套接字添加信息(IP地址和端口)
- listen监听,监听网络连接,监听是否有人连接
- accept接入,监听到有人接入,就接受一个连接
- 数据交流read,write
- close关闭套接字,断开连接
- tcp client步骤
- 先建立socket套接字,返回网络描述符,后面会用到
- connect连接,连接前要知道IP地址和端口
- 数据交流read,write
- close关闭套接字,断开连接
API介绍
- 头文件
- #include <sys/types.h>
- #include <sys/socket.h>
- int socket(int domain,int type, int protocol)
- 一种可用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源的函数
- 返回值:非负描述符 – 成功,-1--出错
- int bind(int sockfd,const struct sockaddr* myaddr,socklen_t addrlen)
- int listen(int sockfd,int backlog)
- int connect(int sockfd,conststruct sockaddr *addr, socklen_t addrlen)
- int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
- ssize_t send(int sockfd,constvoid *buf, size_t len,int flags)
- ssize_t sendto(int sockfd,const void *buf, size_t len,int flags,const struct sockaddr *dst_addr, socklen_t addrlen) 主要用于udp
- ssize_t recv(int sockfd,void *buf, size_t len,int flags)
- ssize_t recvfrom(int sockfd,void *buf, size_t len,int flags,struct sockaddr *src_addr, socklen_t *addrlen) 主要用于udp
- read write
地址转换API
- int inet_aton(const char* straddr , struct in_addr *addrp);
- 把字符串形式的“xxx.xxx.xxx.xxx”转为网络识别模式
- char* inet_ntoa(struct in_addr inaddr);
- 把网络识别模式转为字符串形式的“xxx.xxx.xxx.xxx”
实战 聊天对话框
服务器 运行时后面要传IP地址和端口
#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>
int main(int argc, char **argv)
{
int s_fd;//服务端描述符
int c_fd;//客户端描述符
int n_read;//读了多少字节
char readBuf[128];//收到的数据缓存
int mark = 0;//记录第几个连接标识符
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 AF_INET为ipv4协议 SOCK_STREAM为面向连接的通信流,tcp协议,0是自动匹配对应协议
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;//记录上面的协议族ipv4协议
s_addr.sin_port = htons(atoi(argv[2]));//将字符串端口号转为数字然后转为网络字符
inet_aton(argv[1],&s_addr.sin_addr);//将ip地址转为网络字符放到sin_addr里
//2. bind连接 s_fd是socket描述符 &s_addr是存放socket信息的结构体 sizeof...是结构体的大小
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3. listen监听 s_fd是socket描述符 10是最多有十个人同时连接
listen(s_fd,10);
//4. accept接入
int clen = sizeof(struct sockaddr_in);//是存放socket信息的结构体的大小
while(1){
// s_fd是socket描述符 &c_addr是已连接的客户端的协议结构体 &clen是存放socket信息的结构体的大小
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
}
mark++;//记录第几个连接标识符
printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));//打印客户端的IP地址
//创建线程
if(fork() == 0){
//write写
if(fork()==0){//子线程一直循环发送第几个连接对象,等于心跳包
while(1){
sprintf(msg,"welcom No.%d client",mark);
write(c_fd,msg,strlen(msg));//向描述符发送msg消息
sleep(3);
}
}
//5. read读
while(1){
memset(readBuf,0,sizeof(readBuf));//清空收到的数据
n_read = read(c_fd, readBuf, 128);//接收描述符对应的socket消息,放到readbuf里,最大读128字节
if(n_read == -1){//等于-1 表示读取错误
perror("read");
}else if(n_read>0){//大于0 表示读到数据 并打印出来
printf("\nget: %d\n",n_read);
}else{//其他的表示客户端退出
printf("client quit\n");
break;
}
}
break;
}
}
return 0;
}
客户端 运行时后面要传IP地址和端口
#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>
int main(int argc, char **argv)
{
int c_fd;//客户端描述符
int n_read;//读了多少字节
char readBuf[128];//收到的数据缓存
int tmp;//
char msg[128] = {0};//发送的数据缓存
struct sockaddr_in c_addr;//客户端的协议结构体
memset(&c_addr,0,sizeof(struct sockaddr_in));
if(argc != 3){
printf("param is not good\n");
exit(-1);
}
printf("%d\n",getpid());//打印当前进程的进程ID
//1. socket AF_INET为ipv4协议 SOCK_STREAM为面向连接的通信流,tcp协议,0是自动匹配对应协议
c_fd = socket(AF_INET, SOCK_STREAM, 0);
if(c_fd == -1){
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;//记录上面的协议族ipv4协议
c_addr.sin_port = htons(atoi(argv[2]));//将字符串端口号转为数字然后转为网络字符
inet_aton(argv[1],&c_addr.sin_addr);//将ip地址转为网络字符放到sin_addr里
//2.connect c_fd是socket描述符 &c_addr是存放socket信息的结构体 sizeof...是结构体的大小
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: ");
gets(msg);//
write(c_fd,msg,strlen(msg));//向描述符发送msg消息
}
}
while(1){//
memset(readBuf,0,sizeof(readBuf));
n_read = read(c_fd, readBuf, 128);//接收描述符对应的socket消息,放到readbuf里,最大读128字节
if(n_read == -1){
perror("read");//等于-1 表示读取错误
}else{
printf("\nget:%s\n",readBuf);//大于0 表示读到数据 并打印出来
}
}
}
return 0;
}