1、TCP与UDP的对比:
1、 TCP面向连接(如打电话要先拨号建立连接); UDP是无连接的,即发送数据之前不需要建立连接
2、 TCP提供可靠的服务,也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达; UDP尽最大努力交付,即不保证可靠交付
3、 TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流; UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、 每一条TCP连接只能是点到点的; UDP支持一对一,一对多,多对一和多对多的交互通信
5 、TCP首部开销20字节; UDP的首部开销小,只有8个字节
6、 TCP的逻辑通信信道是全双工的可靠信道, UDP则是不可靠信道
2、什么是端口号
所谓的端口,就好像是门牌号一样, 客户端 可以通过ip地址找到对应的 服务器端 ,但是服务器端是有很多端口的,每个 应用程序 对应一个端口号,通过类似门牌号的端口号,客户端才能真正的访问到该服务器。为了对端口进行区分,将每个端口进行了编号,这就是端口号。
3、端口号的作用
在一台计算机上同时可以进行多个应用程序,例如接受www服务的Web浏览器、电邮客户端、远程登录用的ssh客户端等程序都可以同时进行。传输层协议正是利用这些端口号识别本机中正在进行通信的应用程序,并精准的将数据传输。
4、什么是字节序
字节序是计算机存储多字节数据的方式,目前主流的方式有:大端字节序和小端字节序,字节序主要是针对多字节的数据类型,比如 short、int 等类型
大端字节序(Big endian):高字节存放在低地址,低字节存放在高地址
小端字节序(Little endian):低字节存放在低地址,高字节存放在高地址
网络字节序 = 大端字节序
(1)主机字节序转网络字节序
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); 无符号的32位整型数据的转换
int16_t htons(uint16_t hostshort); 无符号的16位整型数据的转换
功能:htons和htonl都是实现将主机字节序数据转换为网络字节序数据
参数:hostlong和hostshort标识主机字节序数据
返回值:网络字节序数据
(2)网络字节序转主机字节序
#include <arpa/inet.h>
uint32_t ntohl(uint32_t netlong); 无符号的32位整型数据的转换
uint16_t ntohs(uint16_t netshort); 无符号的16位整型数据的转换
参数:netlong和netshort标识网络字节序数据
返回值:主机字节序数据
(3)将字符串IP地址转换为网络字节序的整型数据
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
参数1:af表示地址协议族
AF_INET: 基于IPV4协议族的IP地址
AF_INET6: 基于IPV6协议族的IP地址
参数2:src指针指向的字符串IP地址;
参数3:det目标指针用来返回转换接收的整型数据(网络字节序的IP地址);
返回值:
装换成功返回1;af指定地址协议族中没有找到IP则返回0,否则失败返回-1
(4)将网络字节序数据转换为主机字符串IP地址
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
参数1:af表示地址协议族
AF_INET: 基于IPV4协议族的IP地址
AF_INET6: 基于IPV6协议族的IP地址
参数2:传递网络字节序IP地址的整型数据存储空间的地址;
参数3:返回主机字节序IP地址,
参数4:表示参数3所对对应空间的大小
size可以使用宏定义:
INET_ADDRSTRLEN //ipv4空间大小;
INET6_ADDRSTRLEN //ipv6空间大小;
返回值:成功返回dst的指针,失败返回NULL且修改errno的值。
(5)网络字节序IP和主机字节序IP转换其它API
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
功能: 将cp指向的主机字节序IP地址转换为网络字节序的IP地址存储在结构体指针inp指向的空间中
in_addr_t inet_addr(const char *cp);
功能:将cp指向的主机字节序IP地址转换为网络字节序的IP地址通过函数的返回值返回
in_addr_t inet_network(const char *cp);
功能:将cp指向的主机字节序IP地址转换为网络字节序的IP地址通过函数的返回值返回
char *inet_ntoa(struct in_addr in);
功能:将网络字节序IP地址转为主机字节序的字符串IP地址通过返回值返回。
struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);
5、Socket编程步骤
(1)、创建套接字:socket()
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain: 地址族,常用有AF_INET和AF_INET6,分别代表IPv4和IPv6
type: 套接字类型,常用SOCK_STREAM(流格式套接字/面向连接的TCP套接字),
SOCK_DGRAM(数据报套接字/无连接的UDP套接字)
protocol:默认写0,常用IPPROTO_TCP,IPPROTO_UDP,分贝代表TCP和UDP协议
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0); 创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0); 创建UDP套接字
(2)、为套接字添加信息(IP地址和端口号):bind()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
int sockfd: 是socket描述符
const struct sockaddr *addr: 是一个结构体指针
socklen_t addrlen: 是第二个参数,结构体指针的大小,一般用sizeof()就可以搞定
addr结构体的原型是:
struct sockaddr_in {
sa_family_t sa_family; 协议族
in_port_t sin_port; 端口号
struct in_addr sin_addr; IP地址结构体
unsigned char sin_zero[8]; 填充,没有实际意义,只是为跟sockaddr结构在内存
中对齐,这样两者才能相互转换
}
(3)、地址转换API
把字符串形式的"192.168.1.123"转为网络能识别的格式
int inet_aton(const char* straddr,struct in_addr* addrp);
const char* straddr: 字符串
struct in_addr* addrp: 一个结构体,IP地址
把网络格式的IP地址转为字符串形式
char* inet_ntoa(struct in_addr inaddr);
struct in_addr inaddr: 一个结构体,IP地址
(4)、监听网络连接:listen()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
int sockfd: socket的描述符
int backlog: 支持的最大连接数,一个整数
(5)、监听到有客户端接入,开始接收连接:accept()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int sockfd: socket描述符
struct sockaddr *addr: 客户端的协议地址,一个结构体,如果不关心,传NULL
socklen_t *addrlen: 客户端地址长度,如果不关心,传NULL
(6)、数据收发
数据收发常用第一套API
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
数据收发常用第二套API:有连接
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
socket服务端代码实现(无连接客户端)
server.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main()
{
int s_fd;
//1.socket
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("socked");
exit(-1);
}
struct sockaddr_in s_addr;
s_addr.sin_family = AF_INET; //TPv4因特网域
s_addr.sin_port = htons(8989);
inet_aton("192.168.43.235",&s_addr.sin_addr); //虚拟机linux的ip地址
//2.bind
bind(s_fd, (struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
//4.accept
int c_fd = accept(s_fd,NULL,NULL);
//5.read
//6.write
printf("connect\n");
while(1);
return 0;
}
执行结果:
socket服务端/客户端
server2.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int s_fd;
int n_read;
char readBuf[128];//定义一个数组
char *msg = "I got your message";
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr,0,sizeof(struct sockaddr_in)); //一般来说先清空空间数据,再配置。避免结构体里面有杂乱数据
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("socked");
exit(-1);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8989);
inet_aton("192.168.43.235",&s_addr.sin_addr);
//2.bind
bind(s_fd, (struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
//4.accept
int clen = sizeof(struct sockaddr_in);//长度
int c_fd = accept(s_fd,(struct sockaddr *)&c_addr, &clen);
if(c_fd == -1){
perror("accept");
}
//成功则打印客户端ip地址
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr)); //inet_ntoa()函数,把网络格式的 IP 地址转为字符串形式
//==================服务端与客户端之间数据交互=============================
//5.read
n_read = read(c_fd,readBuf,128);//把客户端的内容读到readBuf
if(n_read == -1){
perror("read");
}else{
printf("get message: %d,%s\n",n_read,readBuf);//打印readBuf中内容
}
//6.write
write(c_fd,msg,strlen(msg));//写入操作
//======================================================================
return 0;
}
客户端
1.客户端实现步骤
2.使用socket()函数创建套接字
3.调用connect()函数建立一个与TCP服务器的连接
4.发送数据请求,接收服务器的数据应答
5.终止连接
创建套接字:socket()
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain: 地址族,常用有AF_INET和AF_INET6,分别代表IPv4和IPv6
type: 套接字类型,常用SOCK_STREAM(流格式套接字/面向连接的TCP套接字),
SOCK_DGRAM(数据报套接字/无连接的UDP套接字)
protocol:默认写0,常用IPPROTO_TCP,IPPROTO_UDP,分贝代表TCP和UDP协议
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0); 创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0); 创建UDP套接字
2、连接服务器(主机):connect()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
int sockfd: 客户端的socket描述符
const struct sockaddr *addr: 客户端服务器的IP地址和端口号,一个结构体
socklen_t addrlen: 是第二个参数,结构体指针的大小,一般用sizeof()就可以搞定
client.c
#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>
#include <unistd.h>
int main()
{
int c_fd;
int n_read;
char readBuf[128];
char *msg = "msg from client";
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
perror("socked");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(8989);
inet_aton("192.168.43.235",&c_addr.sin_addr);
//2.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){
perror("connect");
exit(-1); //连接不到服务端会阻塞,出错直接跳出程序,以免搞崩代码
}
//3.send
write(c_fd,msg,strlen(msg));
//==================读取服务端的信息==================
//4.read
n_read = read(c_fd,readBuf,128);
if(n_read == -1){
perror("read");
}else{
printf("get message from server: %d ,%s\n",n_read,readBuf);
}
//===================================================
return 0;
}
执行结果:
6.用Socket实现双方聊天
Socket实现双方聊天——服务端代码
#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>
#include <unistd.h>
int main(int argc,char **argv){
int s_fd;
int c_fd;
int n_read;
char readBuf[128];
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));
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
printf("socket create fail\n");
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr));
listen(s_fd,10);
int clen = sizeof(struct sockaddr);
while(1){
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
}
printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
if(fork() == 0){
if(fork() == 0){
while(1){
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
while(1){
memset(readBuf,0,sizeof(readBuf));
n_read = read(c_fd,readBuf,128);
if(n_read == -1){
perror("read");
}else{
printf("get message %d,%s\n",n_read,readBuf);
}
}
}
}
return 0;
}
Socket实现双方聊天——客户端代码
#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>
#include <unistd.h>
int main(int argc,char **argv){
int c_fd;
int n_read;
char readBuf[128];
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);
}
//1.socket
c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
printf("socket create fail\n");
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);
//2.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){
perror("connect");
}
while(1){
if(fork() == 0){
//3.write
while(1){
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
//4.read
while(1){
memset(readBuf,0,sizeof(readBuf));
n_read = read(c_fd,readBuf,128);
if(n_read == -1){
perror("read");
}else{
printf("get message from server %d,%s\n",n_read,readBuf);
}
}
}
return 0;
}
执行结果: