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地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等
这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP地址与网络服务的关系是一对多的关系。
实际上是通过IP地址+端口号来区分不同的服务的。
端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。
例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69
3、字节序
字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
常见序:
1. Little endian(小端字节序):将低序字节存储在起始地址
2. Big endian(大端字节序): 将高序字节存储在起始地址
网络字节顺序采用big endian排序方式
例子:在内存中双字0x01020304(DWORD)的存储方式
LE 04 03 02 01
BE 01 02 03 04
4、Socket服务器和客户端的开发步骤
1)创建套接字
2)为套接字添加信息(IP地址和端口号)
3)监听网络连接
4)监听到有客户端接入,接受一个连接
5)数据交互
6)关闭套接字,断开连接
5、Linux的API
1.创建套接字
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_RAW
允许程序使用低层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
*protocol
通常赋值为0.
*0选择type类型对应的默认协议
*IPPROTO_TCP 传输协议
*IPPROTO_UDP 传输协议
*IPPROTO_SCTP SCTP传输协议
*IPPROTO_TIPC TIPC传输协议
返回值:socket描述符,sockfd
2.为套接字添加信息(IP地址和端口号)
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:用于绑定IP地址和端口号到socketfd
参数:
*sockfd 是一个socket描述符
*addr 是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,只想要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同
*addrlen 结构体sockaddr的长度
struct sockaddr_in{
sa_family_t sin_family;//协议族
in_port_t sin_port;//端口号
struct in_addr sin_addr;//IP地址结构体
unsigned char sin_zero[8]//填充,没有实际意义,只是与sockaddr结构在内存中对齐,两者才能转换
}
3.地址转换API
int inet_aton(const char* straddr, struct in_addr*addrp);
把字符串形式的“192.168.1.123”转为网络能识别的格式
参数:straddr字符串形式
addrp 网络ip
char* inet_ntoa(struct in_addr inaddr);
把网络格式的IP地址转为字符串形式
字节序转换API
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue);//返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue);//返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue);//返回主机字节序的值uint32_t
ntohl(uint32_t net32bitvalue);//返回主机字节序的值
h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取
4.监听
int listen(int sockfd, int backlog);
功能:
设置能处理的最大连接数,listen()并未开始接受连线,只是设置sockect的listen模式,listen函数只能用于服务器端,服务器进程不知道与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应连接请求,并对他作出处理,一个服务进程可以同时处理多个进程的连接。主要就两个功能:将一个未连接的套接字转换给一个被动套接字(监听)规定内核为相应套接字排队的最大连接数。
内核为任何一个给定监听套接字维护两个队列
1.未完成连接队列,每个这样的SYN报文段对应其中一项,已由某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程,这些套接字处于SYN_REVD状态;
2.已完成连接队列,每个已完成TCP三次握手过程的客户端对应其中一项。这些套接字处于ESTABLISHED状态。
参数:
sockfd 是socket系统调用返回的服务器端socket描述符
backlog 指定在请求队列中允许的最大请求数
5、连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:
accept函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。
参数
sockfd 是socket系统调用返回的服务器端socket描述符
addr 用来返回已连接的客户端的协议地址
addrlen 客户端地址长度
返回值
该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示TCP三次握手已完成),当服务器完成对某个给定客户的服务室,相应的已连接套接字就会被关闭。
客户端连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd 是socket系统调用返回的服务器端socket描述符
addr 用来返回已连接的客户端的协议地址
addrlen 客户端地址长度
6、数据收发
在套接字通信中进行字节读取函数,read(),write(),与I/O中的读取函数略有区别,因为他们输入与输出的字节数比可能比请求的少。
ssize_t write(int fd, const void *buf, size_t nbytes);
ssize_t read(int fd, void *buf, size_t nbyte);
函数返回读或写的字节数个数,出错则返回-1
例子:实现双方聊天
服务器端serve.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv)//传参,IP地址+端口号
{
int s_fd;
int c_fd;
char readBuf[128];
int n_read = 0;
char msg[128] = {0};
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);
s_addr.sin_family = AF_INET;//
s_addr.sin_port = htons(atoi(argv[2]));//端口号字节序转换
inet_aton(argv[1], &s_addr.sin_addr);//IP地址转换
//2.bind绑定IP地址和端口号
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);
while(1){
c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);
if(c_fd == -1){
perror("accept");
}
printf("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);//获取键盘输入
//6.write
write(c_fd, msg, strlen(msg));
}
}
while(1){
//5.read
memset(readBuf, 0, sizeof(readBuf));
n_read = read(c_fd, readBuf, 128);
printf("get message:%d, %s\n", n_read, readBuf);
}
break;
}
}
//7.close
close(c_fd);
return 0;
}
客户端client.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int c_fd;
char readBuf[128];
int n_read;
char msg[128]={0};
struct sockaddr_in c_addr;
memset(&c_addr, 0, sizeof(struct sockaddr_in));
//1.socket
c_fd = socket(AF_INET, SOCK_STREAM, 0);
c_addr.sin_family = AF_INET;//
c_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1], &c_addr.sin_addr);
//2.connect,客户端连接
connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr));
while(1){
if(fork()==0){
while(1){
memset(msg, 0, sizeof(msg));
printf("input:");
gets(msg);
//3.write
write(c_fd, msg, strlen(msg));
}
}
while(1){
memset(readBuf,0,sizeof(readBuf));
//4.read
n_read = read(c_fd, readBuf, 128);
printf("get message:%d, %s\n", n_read, readBuf);
}
}
//5.close
close(c_fd);
return 0;
}
运行结果
ltk@ubuntu:~/06.30/pthread$ ./serve 192.168.0.175 8897
connect: 192.168.0.175
input:get message:5, nihao
nihao
input:kangxiansen hen shuai
input:get message:5, shide
ltk@ubuntu:~/06.30/pthread$ ./client 192.168.0.175 8897
input:nihao
input:get message:5, nihao
get message:21, kangxiansen hen shuai
shide
input: