socket套接字
socket是一个编程接口(网络编程接口) 作用是用来实现网络上不同的主机的应用进程之间进行双向通信
套接字是一种特殊的文件描述符 也就意味着我们使用套接字实现网络通信的时候可以用read/write
比如:客户端可以用write发送网络数据 服务器端可以用read去接收网络数据
要通过互联网进行通信 至少需要一对套接字 其中一个运行在客户端 称之为Client Socket
另一个运行在服务器端 称之为Server Socket
Socket可以分为三种类型:
1) 流套接字(SOCK_STREAM)
流套接字用于提供面向连接 可靠的数据传输服务
主要针对传输层协议为TCP协议的应用
如果等待我们所写的代码利用TCP协议来通信的话 那么我们就需要创建一个流套接字
2) 数据报套接字(SOCK_DGRAM)
数据报套接字提供一种无连接的服务(并不能保证数据传输的可靠性)
主要针对传输层协议为UDP协议的应用
3) 原始套接字(SOCK_RAW)
TCP UDP 特点
TCP特点:
1、tcp是面向连接的,通信之前需要建立连接,通信结束之后还需释放连接**(三次握手,四次挥手)**
2、tcp提供了很可靠的支付服务,可靠也就是说:tcp的数据没有重复、没有丢失、没有错误、并且和发送端的数据是一致的。
3、tcp是面向字节流的。也就是tcp是以字节为单位,虽然传输的过程中数据被划分为了一个一个数据报文,但是这只是为了方便传输,接收端最终接受到的数据和发送端接收到的数据是一样的。
4、tcp提供全双工通信:就是tcp的两端即可以作为发送端,也可以作为接收端。
5、最重要的一点就是一个tcp的连接只能有两个端点,支持一对一通信。
6、tcp首部含有20个字节。
UDP特点:
1、首先udp是无连接的,通信结束也不需要释放连接。
2、upd是一种不可靠的协议,发出去就不管了。
3、udp是一种面向报文的链接,udp数据传输的单位是报文,而且不会对数据做任何的拆分和拼接操作。在发送端,应用程序给传输层的udp什么样的数据,udp不会对数据进行拆分,最会增加一个udp头并且交给网络层。在接收端,udp收到网络层的数据之后,除去ip(网络层协议)数据报头部后便交给应用层,不会做任何的拼接操作。
4、udp是不存在拥塞控制的,并且始终就是用恒定的速率发送数据,并不会根据网络拥塞情况对发送速率做调整。这个状况下就会存在优势和弊端;弊端就是:网络拥塞时有些报文就会丢失,所以才说udp是不可靠的协议;他的优点就是有些使用场景允许报文丢失,比如:直播,语音通话,但是对实时性要求比较高。
5、udp支持一对一,一对多,多对多,多对一通信。
6、udp首部的开销比较小,只有8个字节。相对于tcp来说,效率还是很高的。
TCP UDP 优缺点
TCP优点:可靠,稳定。tcp的可靠体现在tcp在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认,窗口,重传,拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。
TCP缺点:速度慢,效率低,占用系统资源高,易被攻击。TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制,重传机制,拥塞机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU,内存等硬件资源。而且,因为TCP有确认机制,三次握手机制,这些也导致TCP容易被人利用,实现DOS,DDOS,CC等攻击。
UDP优点:速度快,比TCP稍安全。UDP没有TCP的握手,确认,窗口,重传,拥塞控制等机制。UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的。比如:UDP Flood攻击。
UDP缺点:不可靠,不稳定。因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会容易丢包。
————————————————
版权声明:本文为CSDN博主「晗二狗」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/skitan/article/details/107444486
TCP套接字编程流程:
TCP网络应用的数据传输的大概过程
建立连接
“三次握手”
发送/接收网络数据
write/send/sendto
read/recv/recvfrom
关闭连接
“四次挥手”
为什么会有三次握手的机制?
通信双方成功通信的前提条件是双方都要能够建立连接。
那么双方都能连接到一起的前提条件是什么呢?
必须双方都能够收和发。
就比如x想跟y聊天 在聊天之前就需要测试x和y能否能聊天,如果不能聊天 则不建立连接。
比如:x是聋子(不能收)或则y是哑巴(不能发) 则连接就不能成功建立, 只有当x和y都不
是聋子和哑巴的时候 连接才能建立成功!
所以三次握手实际上就是一个测试能不能建立连接的过程。
第一次握手就是测试客户端能不能发
第二次握手就是测试服务器端能不能收和发
第三次握手就是测试客户端能不能收
“四次挥手”:
建立连接是非常重要的 他是数据正确传输的前提 断开连接 他让计算机释放不再使用的资源
四次挥手的具体过程:
建立连接后 客户端和服务器端都处于ESTABLISHED状态 客户端发起断开连接的请求
1)客户端调用close函数 向服务器发送FIN数据包 进入FIN_WAIT_1状态
FIN就是表示断开连接 FINISH
2)服务器收到数据包后 检测到设置了FIN标志位 知道要断开连接。于是向客户端发送”确认包“
键入CLOSE_WAIT
注意:服务器收到请求后并不是立即断开连接 而是先向服务器端发送”确认包“
告诉他我知道了 我需要准备一下才能断开连接
3)客户端收到”确认包“ 后进入FIN_WAIT_2状态 等待服务器准备完毕之后再发一个数据包过来
4)等待片刻之后 服务器准备完毕了 可以断开连接 于是再主动地向客户端发送FIN 告诉它我准备
好了 断开连接把 然后进入LAST_ACK状态
5)客户端收到服务器地FIN包后 再向服务器发送ACK包 告诉他你断开连接把 然后进入TIME_WAIT状态
6)服务器收到客户端的ACK包后 就断开连接 关闭套接字 进入CLOSED状态
利用Socket套接字实现TCP通信和UDP通信的函数流程
TCP Server:
socket : 绑定一个套接字
bind : 把一个套接字和一个网络地址绑定在一起
如果你想让其他人来主动连接或联系你 你就需要bind一个地址 并把这个地址告诉别人
不调用bind 并不代表你得socket没有地址, 相反 调不调用bind socket在通信的时候 内核都会
动态地为你的socket指定一个地址
listen: 让套接字进入一个”监听模式“
accpet: 接收客户端的连接
多次调用accept就可以与不同的客户端建立连接
write/send/sendto or read/recv/recvfrom
close/shutdown : ”四路挥手“
代码实现:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
int flag=0;
void sys_erro(char * str)
{
perror(str);
}
int main(int argc,char * argv[])
{
if(argc!=3)
{
return -1;
}
//创建套接字
int sock_fd=socket(AF_INET,SOCK_STREAM,0); //ipV4
if(-1==sock_fd)
sys_erro("socket error\n");
//指定服务器
struct sockaddr_in serv;
serv.sin_family=AF_INET;
serv.sin_port=htons(atoi(argv[2])); //端口号 转成16bits
serv.sin_addr.s_addr=inet_addr(argv[1]); //ip
int ret=bind(sock_fd,(struct sockaddr*)&serv,sizeof(serv));//绑定网络地址
if(-1 == ret)
{
close(sock_fd);
sys_erro("bind error\n");
}
//监听套接字
int listen_ret=listen(sock_fd,10);
if(-1 == listen_ret)
{
close(sock_fd);
sys_erro("listen error\n");
}
//阻塞等待连接
struct sockaddr_in Client;
socklen_t len=sizeof(Client);
while (1)
{
int accept_ret=accept(sock_fd,(struct sockaddr*)&Client, &len);
if(-1 == accept_ret)
{
sys_erro("accept error\n");
}
printf("connet [%s] [port:%d]\n",inet_ntoa(Client.sin_addr),ntohs(Client.sin_port));
pid_t pid=fork();
if(pid==0)
{
while (1)
{
char buf1[1024]={
0}; //写
char buf2[1024]={
0}; //读
int Rret;
//接收
Rret=recv(accept_ret,buf2,sizeof(buf2),0);
if(Rret==-1)
{
sys_erro("recv error\n");
}
else if(Rret>0)
{
if(strncmp(buf2,"SeeYou",6)==0)
{
close(accept_ret);
flag=1;
break;
}
printf("服务器接收:%s\n",buf2);
}
//发送
fgets(buf1,1023,stdin); //键盘输入
send(accept_ret,buf1,strlen(buf1),0);
}
}
else if(pid>0)
{
close(accept_ret);
}
else
{
sys_erro("fork erro\n");
}
if(flag==1)
{
break;
}
}
close(sock_fd);
printf("服务器关闭\n");
}
TCP Client:
socket : 绑定一个套接字
bind : 可要可不要
connect : 主动与TCP Server建立连接 ”三次握手“
write/send/sendto or read/recv/recvfrom
close/shutdown
代码实现:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
int sys_erro(char * str)
{
perror(str);
return -1;
}
int main(int argc,char * argv[])
{
if(argc!=3)
{
return -1;
}
//创建套接字
int sock_fd=socket(AF_INET,SOCK_STREAM,0);
if(-1==sock_fd)
sys_erro("socket error\n");
//指定服务器
struct sockaddr_in serv;
serv.sin_family=AF_INET;
serv.sin_port=htons(atoi(argv[2])); //端口号 转成16bits
serv.sin_addr.s_addr=inet_addr(argv[1]); //ip
//请求连接
int connect_ret=connect(sock_fd,(struct sockaddr*)&serv,sizeof(serv));
if(connect_ret==-1)
{
close(sock_fd);
sys_erro("connect error\n");
}
while (1)
{
int Rret;
char buf1[1024]={
0}; //写
char buf2[1024]={
0}; //读
//键盘写
fgets(buf1,1023,stdin);
write(sock_fd,buf1,strlen(buf1));
if(strncmp(buf1,"SeeYou",4)==0)
{
break;
}
//读
Rret=recv(sock_fd,buf2,sizeof(buf2),0);
printf("%s\n",buf2);
}
close(sock_fd);
}
UDP Server:
socket : 绑定一个套接字
bind : 把一个套接字和一个网络地址绑定在一起
如果你想让其他人来主动连接或联系你 你就需要bind一个地址 并把这个地址告诉别人
write/send/sendto or read/recv/recvfrom
close/shutdown
UDP Client:
socket : 绑定一个套接字
write/send/sendto or read/recv/recvfrom
close/shutdown
套接字选项
每个套接字再不同的协议层次(级别level)上有不同的行为属性(选项) 有两个函数用于获取/设置套接字的选项
getsockopt : 获取套接字的选项
setsockopt : 设置套接字的选项
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
函数参数:
sockfd: 你要设置哪个套接字的选项
level: 你要获取的套接字的选项是属于哪个level ---> “查表”
optname:你要获取的那个套接字选项的“名字”宏 ---> “查表”
optval:指向一段空间 这段空间用来保存获取到的选项的值的
因为不同的选项 值的类型不同
如: SO_RCVBUF -> int *
SO_SNDTIMEO -> struct timeval *
...
optlen: 指向一段空间 这段空间保存获取到的选项值的数据长度的
返回值:
成功返回0
失败返回-1 并且errno被设置
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
函数参数:
前面三个跟上面函数类型 指定你要设置哪个套接字的具体哪个选项
optval:指向一段空间 这段空间里面保存了你要设置的值
optlen:你要设置的选项值所占的空间的长度
返回值:
成功返回0
失败返回-1 并且errno被设置
广播和组播(多播)
1.广播 broadcast
广播就是向局域网内所有的人说话 但是广播还是要指定接收者的端口号的
a.只有传输层协议是UDP协议的时候 才支持广播
因为TCP是端对端 广播是一对多
b.广播地址
广播地址是专门用于同时向网络中所有工作站进行发送的一个地址
子网内广播地址:主机号全为1的IP地址就是广播地址
2.多播(组播)multicast
单播用于两个主机之间的端对端的通信 广播用于一个主机对整个局域网上所有的主机进行通信
单播和广播是两个极端
有时候 我们需要对一组特定的主机进行通信 --> 多播
也就是说 处于同一组的主机就能收到数据 不在同一组的主机就收不到数据 —》类似于QQ群
注意:
a.多播也只有传输层协议为UDP时 才支持多播(组播)功能
b.多播地址是IPV4的D类地址
D 1110多播组号(28bit) 224.0.0.0 - 239.255.255.255
多播的编程思路:
多播同样分为服务器(多播发送者)和客户端(多播接收者)
服务器(多播发送者):
1.创建一个套接字 --》 UDP的套接字
2.通过sendto发送信息到一个多播组地址中
多播组地址: D类IP地址 + PORT
3.关闭套接字
注意:服务器(多播发送者)不需要加入多播组 就可以直接向某个多播组发送数据
而如果你想要接收数据 那么就必须要加入多播组
代码实现:
客户端(多播接收者)
1.创建一个套接字--》UDP的套接字
2.加入一个多播组
3.绑定地址
绑定多播组的地址 --》 D类IP + PORT
4.接收多播组的信息
5.也可以发送信息到多播组
6.关闭套接字