概述 #
- 首先网络,一看到这个词,我们就会想到IP地址和端口号,那IP地址和端口各有什么作用呢?
- IP地址如身份证一样,是标识的电脑的,一台电脑只有一个IP地址。
- 端口提供了一种访问通道,服务器一般都是通过知名端口号来识别某个服务。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。
- 网络socket套接字传输协议是TCP和UDP协议,本次学习利用TCP协议传输数据。
TCP与UDP的区别:
- TCP是面向连接的(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽可能最大努力交付,即不保证可靠交付。
- TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等等)。
- 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
- TCP首部开销20个字节;UDP的首部开销小,只有8个字节。
- TCP的逻辑通信信道是全双工的可靠通道,UDP则是不可靠信道。
字节序 #
字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
常见序:
- Little endian 小端字节序:将低序字节存储在起始地址。
- Big endian 大端字节序:将高序字节存储在起始地址。
例:如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为:
地址 | Little endian | Big endian |
---|---|---|
0x0000 | 0xcd | 0x12 |
0x0001 | 0xab | 0x34 |
0x0002 | 0x34 | 0xab |
0x0003 | 0x12 | 0xcd |
网络字节序=大端字节序,X86系列CPU都是小端字节序。
socket套接字开发 #
开发类似于文件操作。先创建socket,来获取描述符,然后绑定、监听、读写、关闭。
开发步骤:
- socket套接字创建
函数原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//执行成
int socket(int domain, int type, int protocol);
参数说明:
domain: 指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族);
- AF_INET IPv4因特网域
- AF_INET6 IPv6因特网域
- AF_UNIX Unix域
- AF_ROUTE 路由套接字
- AF_KEY 密钥套接字
- AF_UNSPEC 未指定
type指定socket的类型:
- SOCK_STREAM:流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性
- SOCK_DGRAM:数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP。
- SOCK_RAM:允许程序使用低层协议,原始套接字允许对底层如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
protocol:通常赋值为0。
- 0 选择 type 类型对应的默认协议
- IPPROTO_TCP TCP传输协议
- IPPROTO_UDP UDP传输协议
- IPPROTO_SCTP SCTP传输协议
- IPPROTO_TIPC TIPC传输协议
- 绑定函数:bind()
功能:用于绑定IP地址和端口号到 socketfd。
函数原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
sockfd:是一个socket描述符。 addr:是一个指向struct sockaddr类型的指针,包含IP地址及端口号等信息,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同。
struct sockaddr {
sa_family_t sa_family; //协议族
char sa_data[14]; //IP地址+端口
} ;
同等替换:
#include<linux/in.h>
struct sockaddr_in {
__kernel_sa_family_t sin_family; //协议族
__be16 sin_port; //端口号
struct in_addr sin_addr; //ip地址结构体
unsigned char sin_zero[8]; //填充 没有实际意义,只是为跟sockaddr结构在内存中字节对齐,
//这样两者才能相互转换
};
- 监听设置函数:listen()
函数原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//成功执行返回0,否则返回-1
int listen(int sockfd, int backlog);
参数说明:
sockfd:socket系统调用返回的服务端socket描述符; backlog:指定在请求队列中允许的最大请求数,也即最大连接数。 功能:设置能处理的最大连接数。在开始只是设置了socket的listen模式,listen函数只用于服务端,服务器进程不知道要与谁连接,因此,它不会主动的,只是一直监听是否有其他客户进程与知连接,然后响应该连接请求处理,一个服务进程可以同时处理多个客户进程的连接。
- 连接函数:accept()
函数原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明:
sockfd: socket系统调用返回的服务端socket描述符; addr: 用来返回已连接的对端(客户端)的协议地址; addrlen: 客户端地址长度。
返回值:
该函数返回值是一个新连接端(客户端)的套接字描述符。内核为每个由服务器进程接受的客户端连接创建一个已连接套接字(表示TCP三次握手已完成),当服务器完成对某个给定客户端的服务时,相应的已连接套接字就会被关闭。
- 地址转换API
函数原型:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//功能:把字符串形式的“192.168.1.123”转为网络能识别的格式,存储在inp中
int inet_aton(const char *cp, struct in_addr *inp);
//功能:把网络格式的IP地址转为字符串形式
char *inet_ntoa(struct in_addr in);
- 字节序转换API
函数原型:
#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代表net
- s代表short(两个字节)
- l 代表long(4个字节)
通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取。
- 数据常用收发函数
在套接字通信中进行字节读写函数:read()、write()。与文件IO中读取函数略有区别,因为它们输入或输出的字节数可能会比请求少,可以进行测试一下,输入或输出的字节数为有效字节数。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count)
ssize_t write(int fd, const void *buf, size_t count);
还可以使用第二套读写函数:send()、recv()。
- send(): 数据发送
函数原型:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数说明:
- sockfd:新连接端(客户端)的套接字描述符,即为accept函数的返回值。
- buf:存放待发送数据;
- len:发送数据的长度;
- flags:为控制选项,一般设置为0。
返回值:成功执行返回发送数据的长度,否则返回-1。
- recv()函数:数据接收
函数原型:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数说明:
sockfd:新连接端(客户端)的套接字描述符,即为accept函数的返回值; buf:存放接收的数据; len:接收数据的长度; flags:为控制选项,一般设置为0。
- 客户端连接函数connect()
功能:该函数用于客户端与服务器建立连接。
函数原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//成功执行返回0,否则返回-1
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
sockfd:是目标服务器的socket套接字描述符; addr:服务器的IP地址和端口号的地址结构指针 addrlen:addr地址长度,通常设置为sizeof(struct sockaddr)。
编程实现server与client建立连接 #
server.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
int s_fd;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset((void *)&s_addr,0,sizeof(struct sockaddr_in));
memset((void *)&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
s_fd=socket(AF_INET,SOCK_STREAM,0);
printf("s_fd=%d\n",s_fd);
if(s_fd==-1)
{
perror("socket");
exit(0);
}
//2.bind
s_addr.sin_family=AF_INET;
s_addr.sin_port =htons(8888);
inet_aton("本机ip地址",&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
//4.accept
int cnt =sizeof(struct sockaddr_in);
int c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&cnt);
if(c_fd==-1)
{
perror("accept");
exit(0);
}else{
printf("get success:%s\n",inet_ntoa(c_addr.sin_addr));
}
//5.read
char readbuff[128]={0};
int nread=read(c_fd,readbuff,128);
if(nread==-1)
{
perror("read");
}
else{
printf("msg data from clinet:%d,%s\n",nread,readbuff);
memset(readbuff,0,128);
}
//6.write
write(c_fd,"I get your connect",strlen("I get your connect"));
//用read()检测客户端是否退出
nread=read(c_fd,readbuff,128);
if(nread==0)
printf("connect end\n");
else
printf("nread:%d\n",nread);
//关闭
close(s_fd);
close(c_fd);
return 0;
}
client.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
int c_fd;
struct sockaddr_in c_addr;
memset((void *)&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1)
{
perror("socket");
exit(0);
}
c_addr.sin_family=AF_INET;
c_addr.sin_port =htons(8888);//端口号一般设置为5000~9000
inet_aton("本机ip地址",&c_addr.sin_addr);
//connect
int ct=connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in));
if(ct==-1)
{
perror("connect");
exit(0);
}
write(c_fd,"I get connect",strlen("I get connect"));
char readbuff[128];
int nread=read(c_fd,readbuff,128);
if(nread==-1)
{
perror("read");
}
else{
printf("read data:%s\n",readbuff);
}
close(c_fd);
return 0;
}