目录
认识ip地址
- ip :(ip地址在网络上需要具有唯一性)用来标识不同的主机地址
- 点分十进制
- 0.0.0.0只能用于接收数据(无法使用),最大为255.255.255.255(广播地址无法使用)
- ip数据包头部中,有两个ip地址,分别叫源地址,和目的地址
- IPV4版本的ip地址是4个字节的整数(unsigned int ipaddr)
- 不够用时的解决方案:
DHCP:动态分配ip地址(谁上网给谁用)
NAT:地址替换(路由器有唯一的IP地址,连接的个台主机(只有局域网的ip地址)共用这个地址,谁上网给谁用(使用时替换成路由器的外网地址)) - IPV6版本的ip地址是128字节的地址
- 不向下兼容IPV4,所有各个软件厂商不愿使用IPV6,所有一直没有推广(没有特殊说明默认就是IPv4)
端口号port:
- 端口号是一个2字节的16位整数
- unsigned short port(0~65535,0-1024不推荐使用 )
- ip地址+端口号 就能够标识网络上的某一台主机的某一个进程
- 端口号用来标识一个进程,告诉操作系统当前数据需要交给那个进程来处理
- 一个端口号和一个进程pid都能标识唯一一个进程,那么两者有什么区别?
- 一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定
- 重启一个程序,进程pid可能会变但是端口号不变
- (sip dip sport dport proto:五元组标识了通信)(原地址,目的地址,源端口,目的端口,通信协议)
传输层协议
tcp(传输控制协议)udp(用户数据报协议 )
传输层有两个协议,两个协议各有不同的特点和应用场景,如何协议进行数据传输,取决于协议的应用场景和我们当前的使用场景。因此两个协议的特点尤为重要:
- tcp协议特点:数据的可靠传输,有连接,面向字节流的数据传输(收发书数据比较灵活,数据无明显边界,容易造成pcb粘包问题,发送和接收时就需要小心)
- udp协议特点:无连接,不可靠 面向数据报数据传输(数据发送的时候有最大长度限制,接受的时候一条一条接收,因为数据有边界,但是不会产生粘包问题)
- tcp可以保证数据的可靠传输,因此常用于数据安全性要求比较高的场景。但是因为要保证可靠传输,因此牺牲了很多性能,它的数据传输性能是低于udp。
- udp因为不需要保证可靠传输,所以数据传输速度快,实时性高,常用于传输音乐,视频。对数据的完整性要求不高,但是实时性要求比较高的场景
网络字节序
大小端
凡是存储大于一个字节的数据都必须转换为网络字节序的数据
- 发送主机通常将发送缓冲区的数据按内存地址从低到高的顺序发出
- 接收主机把从网络上接收到的字节依次保存在接收缓冲区中,也是按内存低地址到高地址保存
- 因此,网络字节流的地址这样规定:先发的数据是低地址,后发的地址是高地址
- TCP/IP协议规定,网络数据应该采用大端的字节序,即低地址高字节
- 不管是大端还是小端,都会按照这个规定
- 如果发送主机是小端,就需要先转换成大端,否则忽略,直接发送
- 网络字节序是大端字节序
- 主机字节序大小端都有可能
- 大小端(硬件决定)MIPS--PISC处理器(大端),x86处理器(小端)
- 大端:低地址存高位
- 小端:低地址存低位
判断大小端代码
#include<stdio.h>
int main()
{
int a = 1;
if(((unsigned char*)(&a))[0] == 1)//低地址存低位:小端
{
printf("little\n");
return 0;
}
else
{
printf("big\n");
return 0;
}
}
转换网络端口函数
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);四字节
uint16_t htons(uint16_t hostshort);两字节
主机字节序-网络字节序
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
网络字节序-主机字节序
h表示host:主机字节序
n:network:网络字节序
l:四字节
s: 二字节
转换网络ip地址
点分十进制转换为网络IP地址
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);//只能转ipv4
char *inet_ntoa(struct in_addr in);//网络字节序转换从点分十进制字符串
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);//可以转换ipv4和ipv6
socket(套接字编程)
socket是一套接口,用于网络编程的接口,同时socket也是一个数据结构
想要开始网络编程,就需要先创建一个套接字(网络编程,第一步永远是创建套接字,套接字创建成功后,我们才可以通过对套接字的操作,才完成网络是数据的传输)
网络通信是网络上两个主机的进程间的通信,这连个主机有一个区分,
一个叫客户端,一个叫服务端,并且永远是客户端首先向服务端发起请求
也就是,服务端在明处,客户端在暗处。比如qq,客户端非常多,腾讯的服务器不知道客户是谁在哪,所以没法向客户端推送消息,只要下载了客户端,
这些服务器信息都已经封装在qq软件中了
使用函数
struct sockaddr_in
struct sockaddr_in这个结构体是ipv4版本的地址信息,包含地址域,端口,ip
地址域(地址类型):AF_INET,AF_INET6
myaddr参数初始化
- 将结构体清零
- 设置地址类型为:AF_INET
- 网络地址为INADDR_ANY
这个宏表示本地的任意ip地址,直到与某个客户端建立了连接时才确定到底用哪个ip地址 - 端口号为SERV_PORT,我们定义为:9000
监听listen()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- listen声明的socket处于监听状态,并且允许最多有backlog个客户端处于连接等待状态
- 如果收到更多的请求连接就忽略,一般不会设置太大(一般是5)
- 成功返回0,失败返回-1
accept()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 三次握手完成后,服务器调用accept()接收连接
- 如果还没有请求,就阻塞等待直到有客户端到来
- 如果给addr传NULL,表示不关心客户端地址
- addrlen参数是一个传入传出参数,传入的是调用者提供的缓冲区addr
的长度,以避免缓冲区溢出的问题,传出的是客户端地址结构体实际长度
一个非常简单的udp服务端程序
服务端:
/* 这是一个非常简单的udp服务端程序
* 功能是:客户端与服务端的聊天程序
*
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
//1. 创建套接字
//int socket(int domain, int type, int protocol);
// domain: 地址域
// AF_INET ipv4协议
// type: 套接字类型
// SOCK_STREAM 流式套接字
// SOCK_DGRAM 数据报套接字
// protocol: 协议类型
// 0-默认;流式套接字默认tcp协议,数据报套接字默认udp协议
// 返回值:套接字描述符,失败:-1
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd < 0) {
perror("socket error!!\n");
return -1;
}
//struct sockaddr
//struct sockaddr_in
//struct sockaddr_in6
//2. 为socket绑定地址信息,确定socket能够操作缓冲区中的哪些数据
// int bind(int sockfd, struct sockaddr *addr,socklen_t addrlen);
// sockfd: 套接字描述符
// addr: 要绑定的地址信息
// addrlen:地址信息的长度
//
// 端口号是0-65535之间的的一个数字,0-1024不推荐使用
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9000);
addr.sin_addr.s_addr = inet_addr("192.168.3.32");
//inet_pton(AF_IENT, "192.168.122.132", &addr.sin_addr);
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(sockfd, (struct sockaddr*)&addr, len);
if (ret < 0) {
perror("bind error");
close(sockfd);
return -1;
}
//3.接收数据
//ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags
// struct sockaddr *src_addr, socklen_t *addrlen);
// sockfd: socket描述符
// buf: 用于将存储接收的数据
// len: 想要接收的数据长度
// flags: 0-默认是说如果缓冲区没有数据,那么我就阻塞等待
// src_addr: 用于确定数据的发送端地址信息
// addrlen: 地址信息的长度
// 返回值:实际接收的数据长度 ,-1:失败
while (1) {
//接收数据
char buff[1024] = {0};
struct sockaddr_in cli_addr;
len = sizeof(struct sockaddr_in);
ssize_t rlen = recvfrom(sockfd, buff, 1023, 0,
(struct sockaddr*)&cli_addr, &len);
if (rlen < 0) {
perror("recvfrom error");
close(sockfd);
return -1;
}
printf("client[%s:%d] say:%s\n",
inet_ntoa(cli_addr.sin_addr),
ntohs(cli_addr.sin_port), buff);
//发送数据
//ssize_t sendto(int sockfd, const void *buf, size_t len,
// int flag, struct sockaddr *dest_addr,socklen_t addrlen)
// sockfd: socket描述符,发送数据的时候就是通过这个socket
// 所绑定的地址来发送
// buf: 要发送的数据
// len: 要发送的数据长度
// flag: 0-默认阻塞式发送
// dest_addr: 数据要发送到的对端地址
// addrlen: 地址信息长度
// 返回值:返回实际的发送数据长度,失败返回-1
memset(buff, 0x00, 1024);
scanf("%s", buff);
sendto(sockfd, buff, strlen(buff), 0,
(struct sockaddr*)&cli_addr, len);
}
close(sockfd);
return 0;
}
用户端:
//这是一个udp简单的网络聊天的客户端程序
//1:创建套接字
//2:绑定地址信息
//3:向服务端发送数据
//4:接收服务端返回的数据
//5: 关闭socket
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main()
{
//创建套接字
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
//2:为套接字绑定地址信息,通常我们不推荐手动绑定地址信息
//因为绑定有可能会因为特殊情况失败,但是客户端发送数据的时候
//具体使用哪个地址和端口都无所谓,只要数据能成功发送就行
//所以我们不手动绑定地址,直到发送数据的时候,操作系统检测
//到socket没有绑定地址,会自动选择合适的地址和端口为
//socket绑定地址,这种绑定一般不会出搓
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9000);
serv_addr.sin_addr.s_addr = inet_addr("192.168.5.129");
socklen_t len = sizeof(struct sockaddr_in);
while(1)
{
//3: 发送
char buff[1024] = {0};
scanf("%s",buff);
sendto(sockfd,buff,strlen(buff),0,
(struct sockaddr*)&serv_addr,len);
//4:接受
memset(buff ,0x000,1024);
ssize_t r_len = recvfrom(sockfd,buff,1023,0,
(struct sockaddr*)&serv_addr,&len);
if(r_len < 0)
{
perror("recvfrom");
return -1;
}
printf("server say:%s\n",buff);
}
close(sockfd);
return 0;
}
一个非常简单的TCP服务程序
服务端
/* 这是一个tcp服务端程序,它的功能是简单的服务端聊天程序
* tcp是有连接的面向字节流的可靠传输
* 1. 创建socket
* 2. 为socket绑定地址
* 3. 开始监听:可以开始接收客户端的连接请求
* 4. 获取连接建立成功的新socket:
* 5. 接收数据
* 6. 发送数据
* 7. 关闭socket
* 监听socket和后边新建立的socket的关系
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
if (argc != 3) {
printf("Usage: ./tcp_server ip port\n");
return -1;
}
//1. 创建socket
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd < 0) {
perror("socket error");
return -1;
}
//2. 为socket绑定地址信息
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(sockfd, (struct sockaddr*)&addr, len);
if (ret < 0) {
perror("bind error");
return -1;
}
//3. 监听
// int listen(int sockfd, int backlog);
// sockfd: socket描述符
// backlog: 最大的同时并发连接数
if (listen(sockfd, 5) < 0) {
perror("listen error");
return -1;
}
while(1) {
int new_sockfd;
struct sockaddr_in cli_addr;
len = sizeof(struct sockaddr_in);
//4. 获取连接成功的socket
//int accept(int sockfd, struct sockaddr *addr,
// socklen_t *addrlen);
// sockfd: socket描述符
// addr: 新建立连接的客户端地址信息
// addrlen:地址信息长度
// 返回值:返回新的socket连接描述符,失败:-1
// new_sockfd 与 lst_sockfd 区别
// lst_sockfd这是一个拉皮条的socket,说所有的连接请求的数
// 据都是发送到这个socket的缓冲区,然后进行处理(会为
// 这个新连接的客户端新建一个socket)
// (这个socket接收到的数据都是连接请求),
// (dip dport)
// new_sockfd这是一个干活的socket,连接建立成功之后,这个s
// ocket有自己的缓冲区,往后这个客户端所发送的数据都
// 是在这个socket的缓冲区中。
// (dip dport sip sport)
// accept函数是一个阻塞型的函数,连接成功队列中如果没有新
// 的连接,那么就会一直阻塞直到有新的客户端连接到来
new_sockfd = accept(sockfd, (struct sockaddr*)&cli_addr,
&len);
if (new_sockfd < 0) {
perror("accept error");
continue;
}
printf("new conn %s:%d\n", inet_ntoa(cli_addr.sin_addr),
ntohs(cli_addr.sin_port));
while(1) {
//5. 接收数据
//ssize_t recv(int sockfd, void *buf, size_t len,int flags)
// sockfd: 建立连接成功的socket描述符
// buf: 用于接收数据
// len: 用于指定接收数据长度
// flags:默认0-阻塞式接收
// 返回值:错误:-1 连接关闭:0 实际接收长度:>0
char buff[1024] = {0};
ssize_t rlen = recv(new_sockfd, buff, 1023, 0);
if (rlen < 0) {
perror("recv error");
close(new_sockfd);
continue;
}else if (rlen == 0) {
printf("peer shutdown!!\n");
close(new_sockfd);
continue;
}
printf("client[%s:%d] say:%s\n",
inet_ntoa(cli_addr.sin_addr),
ntohs(cli_addr.sin_port), buff);
//发送数据
memset(buff, 0x00, 1024);
scanf("%s", buff);
send(new_sockfd, buff, strlen(buff), 0);
}
}
close(sockfd);
return 0;
}
用户端
/* 这是一个tcp的客户端简单聊天程序
* 1. 创建socket
* 2. 为socket绑定地址
* 3. 向服务端发起连接请求
* 4. 发送数据
* 5. 接收数据
* 6. 关闭
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
if (argc != 3) {
printf("Usage:./tcp_client ip port\n");
return -1;
}
//1. 创建socket
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd < 0) {
perror("socket error");
return -1;
}
//2. 绑定:客户端程序不推荐绑定地址
//3. 向服务端发起连接请求
//int connect(int sockfd, struct sockaddr *addr,
// socklen_t addrlen);
// sockfd: socket描述符
// addr: 要连接的服务端地址
// addrlen: 地址信息长度
// 返回值:成功:0 失败:-1
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(atoi(argv[2]));
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t len = sizeof(struct sockaddr_in);
int ret = connect(sockfd, (struct sockaddr*)&serv_addr, len);
if (ret < 0) {
perror("connect error");
return -1;
}
while(1) {
//4. 发送数据
char buff[1024] = {0};
scanf("%s", buff);
send(sockfd, buff, strlen(buff), 0);
//5. 接收数据
memset(buff, 0x00, 1024);
ssize_t rlen = recv(sockfd, buff, 1023, 0);
if (rlen < 0) {
perror("recv error");
return -1;
}else if (rlen == 0) {
printf("peer shutdown!\n");
return -1;
}
printf("server say:%s\n", buff);
}
close(sockfd);
return 0;
}
多线程的tcp简单聊天程序服务端
//这是一个多线程的tcp简单聊天程序服务端
//1:创建socket
//2:绑定地址
//3:开始监听
//4:获取客户端的socket连接
//5:创建线程
//在线程中获取socket连接与客户端进行通信
//6:关闭socket
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<pthread.h>
#include<netinet/in.h>
#include<arpa/inet.h>
void* thr_start(void *arg)
{
int clifd = (int)arg;
while(1)
{
char buff[1024] = {0};
ssize_t ret = recv(clifd,buff,1024,0);
if(ret <= 0)
{
perror("recv");
close(clifd);
break;
}
printf("client say:[%s]\n",buff);
send(clifd,"what???",7,0);
}
return NULL;
}
int create_worker(int clifd)
{
//创建一个线程来单独处理与指定客户端的通信
pthread_t tid;
int ret;
ret = pthread_create(&tid,NULL,thr_start,(void*)clifd);
if(ret != 0)//0成功非0失败
{
printf("pthread error\n");
}
return -1;
}
int main(int argc,char* argv[])
{
int lstfd ,clifd,ret;
socklen_t len;
struct sockaddr_in lst_addr;
struct sockaddr_in cli_addr;
if(argc != 3)
{
printf("Usage:./ ip port");
return -1;
}
lstfd = socket(AF_INET,SOCK_STREAM,0);
if(lstfd < 0)
{
perror("lstfd");
return -1;
}
lst_addr.sin_family = AF_INET;
lst_addr.sin_port = htons(atoi(argv[2]));
lst_addr.sin_addr.s_addr = inet_addr(argv[1]);
len =sizeof(struct sockaddr_in);
ret = bind(lstfd,(struct sockaddr*)&lst_addr,len);
if(ret < 0)
{
perror("bind");
return -1;
}
if(listen(lstfd,5) < 0)
{
perror("listen");
return -1;
}
while(1)
{
clifd = accept(lstfd,(struct sockaddr*)&cli_addr,&len);
if(clifd < 0)
{
perror("accept error");
continue;
}
create_worker(clifd);
}
return 0;
}
客户端就不敲了,上面的客户端可以用在这里
三次握手四次挥手
https://blog.csdn.net/W_J_F_/article/details/84790841