—-socket传输—-
socket传输主要是为了解决不同进程间通信的问题。可分为 TCP 传输 和 UDP传输。 Tcp 是 面向连接的 可靠的 流式服务 。Udp 是 无连接 不可靠 数据报服务。
1. 什么是socket?
socket 称为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。
socket就是该模式的一个实现:即socket是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
socket()函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
2. socket解决什么问题?
socket主要是用来解决网络跨进程通信(IPC)问题。 那网络之间是如何通信的呢?
3. 网络中进程之间如何通信?
通信方式有本地的进程间通信和网络进程间通信 两种。
3.1 本地的进程间通信
a、消息传递(管道、消息队列、FIFO)
b、同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
c、共享内存(匿名的和具名的)
d、远程过程调用(RPC)
本地可以通过进程PID来唯一标识一个进程。
3.2 网络间进程通信
网络间进程是通过不同的主机间进行通信。因此首要问题如何标识一台主机,以及进一步如何标识一台主机上的唯一进程?
TCP/IP协议族帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。
TCP/IP定义:是互联网相关的各类协议族的总称,如TCP,UDP,IP,FTP,HTTP等都属于TCP/IP族内的协议 。
OSI七层模型
TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中
应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
传输层:TCP,UDP
网络层:IP,ICMP,OSPF,EIGRP,IGMP
数据链路层:SLIP,CSLIP,PPP,MTU
每一抽象层建立在低一层提供的服务上,并且为高一层提供服务。
4. socket如何通信?
网络中进程间利用三元组【ip地址,协议,端口】进行通信,而socket就是利用三元组解决网络通信的一个中间件工具。
socket通信的数据传输方式,常用的有两种:
a、SOCK_STREAM:表示面向连接的数据传输方式(TCP)。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常见的 http 协议就使用 SOCK_STREAM 传输数据,因为要确保数据的正确性,否则网页不能正常解析。
b、SOCK_DGRAM:表示无连接的数据传输方式(UDP)。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为 SOCK_DGRAM 所做的校验工作少,所以效率比 SOCK_STREAM 高。
5. 基于UDP的socket编程
UDP 是 无连接 不可靠 数据报服务
所以UDP不需要进行三次握手来建立连接,直接发送数据,不保证可靠交付,不管对方收到还是没有收到。
5.1 UDP C/S编程架构
5.2 UDP C/S端注意点
UDP 客户端(Client)注意点
1、本地 IP、本地端口(我是谁)
2、目的 IP、目的端口(发给谁)
3、在客户端的代码中,我们只设置了目的 IP、目的端口
客户端的本地 ip、本地 port 是我们调用 sendto 的时候 linux 系统底层自动给客户端分配的;分配端口的方式为随机分配,即每次运行系统给的 port 不一样
UDP 服务端(Server)注意点
1、服务器之所以要 bind 是因为它的本地 port 需要是固定,而不是随机的
2、服务器也可以主动地给客户端发送数据
3、客户端也可以用 bind,这样客户端的本地端口就是固定的了,但一般不这样做
5.3. Socket常用函数接口及其原理
5.3.1 socket()
使用socket()函数创建套接字
int socket(int af, int type, int protocol);
功能:
创建一个用于网络通信的socket套接字(描述符)
参数:
family:协议族(AF_INET(IPv4)、AF_INET6(IPv6)、PF_PACKET(链路层编程)等 。
type:套接字类(SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据报式套接字)、SOCK_RAW(原始套接字)等)。
protocol:协议类别(0、IPPROTO_TCP、IPPROTO_UDP等)。
返回值:
套接字
特点:
创建套接字时,系统不会分配端口
创建的套接字默认属性是主动的,即主动发起服务的请求;
当作为服务器时,往往需要修改为被动的
头文件:
#include <sys/socket.h>
IPv4及IPv6套接字地址结构
IPv4套接字地址结构 sockaddr_in
#include <netinet/in.h>
struct in_addr
{
in_addr_t s_addr;//4字节
};
//IPv4地址结构
struct sockaddr_in
{
sa_family_t sin_family;//2字节 协议
in_port_t sin_port;//2字节 端口PORT
struct in_addr sin_addr;//4字节 IP地址
char sin_zero[8]//8字节 必须为0
};
IPv6套接字地址结构 sockaddr_in6 (非本文重点)
IPv4,IPv6通用地址结构:struct sockaddr
struct sockaddr {
sa_family_t sa_family; // 2字节
char sa_data[14] //14字节
};
应用场景:
当调用编程接口函数,且该函数需要传入地址结构时需要用struct sockaddr进行强制转换
struct sockaddr_in my_addr;
bind(sockfd,(sockaddr*)&my_addr,sizeof(my_addr));
5.3.2 bind()
**让套接字 拥有一个固定的IP、端口信息**
bind只能绑定自身的IP
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
功能:
将本地协议地址与sockfd绑定
参数:
sockfd: socket套接字
myaddr: 指向特定协议的地址结构指针
addrlen:该地址结构的长度
返回值:
成功:返回0
失败:其他
5.3.3 sendto()(UDP)/ send()(TCP)
**发送udp数据**
ssize_t sendto(int sockfd,const void *buf,size_t nbytes,int flags,const struct sockaddr *to, socklen_t addrlen);
功能:
向to结构体指针中指定的ip,发送UDP数据
参数:
sockfd:套接字
buf: 所要发送数据的内容。发送数据缓冲区
nbytes: 所要发送数据的内容大小。发送数据缓冲区的大小
flags:一般为0
to: 指向目的主机地址结构体的指针
addrlen:to所指向内容的长度
注意:
通过to和addrlen确定目的地址
可以发送0长度的UDP数据包
返回值:
成功:发送数据的字符数
失败: -1
5.3.4 recvfrom()(UDP)/recv()(TCP)
接受数据
ssize_t recvfrom(int sockfd, void *buf,size_t nbytes,int flags,struct sockaddr *from, socklen_t *addrlen);
功能:
接收UDP数据,并将源地址信息保存在from指向的结构中
参数:
sockfd: 套接字
buf: 接收数据缓冲区
nbytes: 接收数据缓冲区的大小
flags: 套接字标志(常为0)
from: 源地址结构体指针,用来保存数据的来源
addrlen: from所指内容的长度
注意:
通过from和addrlen参数存放数据来源信息
from和addrlen可以为NULL, 表示不保存数据来源
返回值:
成功:接收到的字符数
失败: -1
5.3.5 connect() (TCP)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
5.3.6 listen() (TCP)
int listen(int sockfd, int backlog);
5.3.7 accept() (TCP)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
5.4 代码示例
5.4.1 proto.h
#ifndef PROTO_H__
#define PROTO_H__
#define RCVPORT "1998"
#define NAMESIZE 1024
struct msg_st
{
char name[NAMESIZE];
uint32_t math;
uint32_t chinese;
}__attribute__((packed)); //取消字节对齐
#endif
5.4.2 client.cpp
socket – sendto – close
#include <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "proto.h"
int main()
{
int fd;
// iPv4 结构体 sockaddr_in
struct sockaddr_in raddr;
struct msg_st rbuf;
//创建一个udp套接字
fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd<0)
{
perror("socket():");
exit(1);
}
//协议
raddr.sin_family = AF_INET;
//将主机字节序转换成网络字节序
raddr.sin_port = htons(atoi(RCVPORT));
//将字符串转换成32位整形数据 赋值IP地址
inet_pton(AF_INET,"0.0.0.0",&raddr.sin_addr);
cout<<"输入您的内容:"<<endl;
scanf("%s",rbuf.name);
//发送数据
if(sendto(fd,&rbuf,sizeof(rbuf),0,(sockaddr *)&raddr,sizeof(raddr) )<0 )
{
perror("sendto()");
exit(1);
}
//关闭套接字
close(fd);
exit(0);
}
5.4.3 server.cpp
socket – bind – recvfrom – close
#include <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "proto.h"
int main()
{
int fd;
struct sockaddr_in laddr,raddr;
struct msg_st rbuf;
socklen_t addrlen;
fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd<0)
{
perror("socket():");
exit(1);
}
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET,"0.0.0.0",&raddr.sin_addr);
//收取数据要bind
if(bind(fd,(sockaddr *)&raddr,sizeof(raddr) ) <0)
{
perror("bind():");
exit(1);
}
addrlen = sizeof(laddr);
while(1)
{
if(recvfrom(fd,&rbuf,sizeof(rbuf),0,(sockaddr *)&laddr,(socklen_t *)&addrlen )<0 )
{
perror("sendto()");
exit(1);
}
cout<<rbuf.name <<endl;
}
close(fd);
exit(0);
}
6.基于TCP的socket编程
注意要点:
TCP连接过程中应用层接口的各个含义原理
TCP数据报结构
三次握手建立连接原理
四次挥手断开连接原理
6.1 TCP C/S编程架构
6.2 TCP C/S 一般流程
Client(客户端): socket() -> connect() -> send()/recv() -> close()
Server(服务端):socket() -> bind() -> listen() -> accept() -> recv()/send() -> close()
6.3 代码示例
6.3.1 client.cpp
#include <iostream>
using namespace std;
#include <cstring>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
int main()
{
//client ----- socket -> connect -> send -> close
//1.socket
int c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1)
cout << "socket():" <<strerror(errno) <<endl;
//2.connect
sockaddr_in c_addr;
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(1989);
inet_pton(AF_INET,"127.0.0.1",&c_addr.sin_addr);
cout <<"?"<<endl;
int ret =connect(c_fd, (sockaddr *)&c_addr,sizeof(c_addr));
if(ret == -1)
cout<<"connect():" <<strerror(errno) <<endl;
//3.send
while(true)
{
char buf[3] = "NO";
if ( send(c_fd,buf,sizeof(buf),0) ==-1)
cout<<"send():" <<strerror(errno) <<endl;
cout << "send successed! " <<endl;
sleep(2);
}
//4.close
close(c_fd);
return 0;
}
6.3.2 server.cpp
#include <iostream>
using namespace std;
#include <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
int main()
{
int cnt=0;
//server socket->bind->listen->accept->recv->close
//1.socket
int listenfd = socket(AF_INET,SOCK_STREAM,0);
if(listenfd == -1)
{
cout<< "socket():" <<strerror(errno) <<endl;
return -1;
}
//2.bind
sockaddr_in bindaddr;
bindaddr.sin_family = AF_INET;
bindaddr.sin_port = htons(1989);
inet_pton(AF_INET,"0.0.0.0",&bindaddr.sin_addr);
if ( bind(listenfd,(sockaddr *)&bindaddr,sizeof(bindaddr)) == -1)
{
cout<< "bind():" <<strerror(errno) <<endl;
return -1;
}
//3.listen
if ( listen(listenfd,8) == -1)
{
cout<< "listen():" <<strerror(errno) <<endl;
return -1;
}
//4.accept
sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr);
int clientfd = accept(listenfd, (sockaddr *)&clientaddr,&clientaddrlen);
if(clientfd ==-1)
{
cout<< "accept():" <<strerror(errno) <<endl;
return -1;
}
while(true)
{
char recvbuf[3];
if(recv(clientfd,recvbuf,sizeof(recvbuf),0) ==-1)
{
cout << "recv():" << strerror(errno) <<endl;
return -1;
}
++cnt;
cout << cnt <<"--connection is suceessed ! "<<endl;
}
//close(clientfd);
close(listenfd);
return 0;
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com