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则是不可靠信道
字节序转换(网络中采用大端排序)
不同类型的 CPU 对变量的字节存储顺序可能不同:有的系统是高位在前,低位在后,而有的系统是低位在前,高位在后,而网络传输的数据顺序是一定要统一的。所以当内部字节存储顺序和网络字节顺序不同时,就一定要进行转换.网络字节顺序采用big endian排序方式.
#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表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长整数从主机字节
序转换为网络字节序,例如将IP地址转换后准备发送。如果主机是小端字节序,这些函数将参数做相应的大小端转
换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回
htons
把unsigned short类型从主机序转换到网络序
htonl
把unsigned long类型从主机序转换到网络序
ntohs
把unsigned short类型从网络序转换到主机序
ntohl
把unsigned long类型从网络序转换到主机序
传输模型
服务器:
1. 创建一个socket,用函数socket()
2. 绑定IP地址、端口等信息到socket上,用函数bind()
3.设置允许的最大连接数,用函数listen()
4.接收客户端上来的连接,用函数accept()
5.收发数据,用函数send()和recv(),或者read()和write()
6.关闭网络连接
客户端:
1.创建一个socket,用函数socket()
2.设置要连接的对方的IP地址和端口等属性
3.连接服务器,用函数connect()
4.收发数据,用函数send()和recv(),或者 read()和write()
5.关闭网络连接
6.函数
SOCKET
bind();细节配置
2)ip地址点分制和数字转换
IP地址通常由数字加点(192.168.0.1)的形式表示,而在struct in_addr中使用的是IP地址是由32位的整数表示的,为了转换我们可以使用下面两个函数:
int inet_aton(const char *cp,struct in_addr *inp)表示将a.b.c.d形式的IP转换为32位的IP,
inp指针里面。char *inet_ntoa(struct in_addr in) //是将32位IP转换为a.b.c.d的格式
listen()
accept()
write()、read()、send()、recv()
connect()
//服务器
//1socket说我要网络、2bind约定协议ip端口号、3listen监听等你来噢
//4accept连接判断三次握手成功返回fd 并把发送端得数据存放在c_ddr里面 打印ip
//5双方 读 写数据
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
//struct sockaddr_in {
// __kernel_sa_family_t sin_family; /* Address family */
// __be16 sin_port; /* Port number */
// struct in_addr sin_addr; /* Internet address */
//}
int main()
{
int acceptLne = sizeof(struct sockaddr_in); //accept第三个参数大小
int n_read;
char readBuf[128];//用于接收客户端数据
char *writeBuf = "this is server send!!\n";//服务器发送数据
int n_write;
struct sockaddr_in s_addr;//结构体中存放服务器 协议 端口 ip
struct sockaddr_in c_addr;//结构体中存放客户端 协议 端口 ip
memset(&s_addr,0,sizeof(struct sockaddr_in)); //void *memset(void *s, int c, size_t n);//清空防止第二次 乱码
memset(&c_addr,0,sizeof(struct sockaddr_in)); //void *memset(void *s, int c, size_t n);
//socket int socket(int domain, int type, int protocol);
int s_fd = socket(AF_INET,SOCK_STREAM,0);// 1 我要网络、ipv4、tcp、自动配置
if(s_fd == -1){
printf("scket failer!!\n");
perror("why");
}
//bind int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(5555);
inet_aton("192.168.1.8",&s_addr.sin_addr); //int inet_aton(const char *cp, struct in_addr *inp);
// 2 bind 存放socket的fd,强转addr*到addr_in*类型---放协议族、端口htons、
// inet_aton()将ip阿斯克码 转换成网络字节序
int bind_fd = bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
if(bind_fd == -1){
printf("bind failer!!\n");
perror("why");
}
//listen int listen(int sockfd, int backlog);
listen(s_fd,10); // 3 监听fd 监听个数
//accept 连接会判断三次握手 返回值存放在新的c_fd里面
//s_fd,第二个参数是存放客户端的信息 协议族IPV4、端口,ip这些,第三个计算该长度
//accept int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&acceptLne);
if(c_fd == -1){
printf("accept failer!!\n");
}
printf("ip:%s\n",inet_ntoa(c_addr.sin_addr));// char *inet_ntoa(struct in_addr in); //把网络格式转换成计算机识别的阿斯克吗
// 4 读取客户端发过来的内容
//read ssize_t read(int fd, void *buf, size_t count);
n_read = read(c_fd,readBuf,128);
printf("read:%d %s\n",n_read,readBuf);
// 向客户端发送内容
//write ssize_t write(int fd, const void *buf, size_t count);
write(c_fd,writeBuf,strlen(writeBuf));//不能用sizeof 计算的是指针的大小
return 0;
}
//客户端
// socket 与服务器配置一样
//accept 和bind配置差不多 连接上后 一个发一个收
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
//struct sockaddr_in {
// __kernel_sa_family_t sin_family; /* Address family */
// __be16 sin_port; /* Port number */
// struct in_addr sin_addr; /* Internet address */
//}
int main()
{
char *writeBuf = "connect send!\n";//客户端发送数据 服务器收
int n_read;
char readBuf[128]; //用于接收服务器发的数据
struct sockaddr_in c_addr; //定义这样一个结构体存放 协议族ipc4 端口号 ip
memset(&c_addr,0,sizeof(struct sockaddr_in));//清空结构体里面的东西方便下次连接
//socket int socket(int domain, int type, int protocol);
int c_fd = socket(AF_INET,SOCK_STREAM,0);// 1 socket 我要和服务器一样的网络ipv4 tcp
if(c_fd == -1){
printf("scket failer!!\n");
perror("why");
}
// int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(7555);
inet_aton("192.168.1.8",&c_addr.sin_addr); //int inet_aton(const char *cp, struct in_addr *inp);
int connect_fd = connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)); // 2 连接网络connet socket-fd 结构体强转addr类型、计算这个结构体大小
if(connect_fd == -1){
printf("connect failer!!\n");
perror("why");
}
// 3 往服务器发内容--》服务器收到打印---》服务器往客户端发内容---》客户端收到打印
//write ssize_t write(int fd, const void *buf, size_t count);
write(c_fd,writeBuf,strlen(writeBuf));
printf("kehuduan write:%s\n",writeBuf);
// 4 读取服务器的内容
//read ssize_t read(int fd, void *buf, size_t count);
n_read = read(c_fd,readBuf,128);
if(n_read ==- 1){
printf("read error!!\n");
}
else {
printf("read:%d %s\n",n_read,readBuf);
}
return 0;
}
服务器与多个客户端通信
服务器端
//服务器端 创建多个子进程与多个客户端通信 gets(writeBuf)用法
//父亲门口接待 儿子不断读取 孙子不断写内容
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
// int socket(int domain, int type, int protocol);
// int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
// int inet_aton(const char *cp, struct in_addr *inp);
// int listen(int sockfd, int backlog);
// int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//struct sockaddr_in {
// __kernel_sa_family_t sin_family; /* Address family */
// __be16 sin_port; /* Port number */
// struct in_addr sin_addr; /* Internet address */
// ssize_t read(int fd, void *buf, size_t count);
int main(int argc,char **argv) //通过main传入ip地址和端口号
{
int s_fd;
int c_fd;
int bind_fd;
int cLen = sizeof(struct sockaddr_in);
int n_read;
char readBuf[128] = {0};
char writeBuf[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));
if(argc != 3){ //判断传入参数是不是三个
perror("argc error");
}
s_fd = socket(AF_INET,SOCK_STREAM,0);// 1 创建网络 返回套接字
if(s_fd == -1){
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));//asscm转换成整型数 再把主机转换成网络短字符
inet_aton(argv[1],&s_addr.sin_addr); //把字符串转换成网络字节序
// 2 绑定套接字 绑定ipv4 ip(强转类型) 端口号
bind_fd = bind(s_fd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in));
if(bind_fd == -1){
perror("bind");
exit(-1);
}
listen(s_fd,10); // 3 最大监听网络个数
while(1){ // 4 来一个连接一个 不断等待连接---父亲
c_fd = accept(s_fd,(struct sockaddr*)&c_addr,&cLen);
if(c_fd == -1){
perror("accept");
}
else{
printf("IP:%s\n",inet_ntoa(c_addr.sin_addr));
}
if(fork() == 0){ //创建子进程 while1 不断读取
if(fork() == 0){
while(1){ // 6 再创建一个子进程 不断输入
//也可定义一个mark 用sprintf
//sprintf(writeBuf,"server --%d---cilent\n",mark);
//write(c_fd,writeBuf,sizeof(writeBuf));
memset(writeBuf,0,sizeof(writeBuf));//清空
printf("input: "); //提醒输入
gets(writeBuf); //输入 输入的值存放再writeBuf数组中
write(c_fd,writeBuf,sizeof(writeBuf));//写
}
}
while(1){ // 5 不断等待读取 每次都先清空readBuf里面的值
memset(readBuf,0,sizeof(readBuf));
//先清空--程序先执行到这句再读取内容
n_read = read(c_fd,readBuf,128);//等待读取内容
if(n_read == -1){
perror("read");
}
else{
printf("sercer rcv:%d %s\n",n_read,readBuf);
}
}
}
}
return 0;
}
客户端
//通过socket 和c_addr配置和服务器连接
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
// int socket(int domain, int type, int protocol);
// int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
// int inet_aton(const char *cp, struct in_addr *inp);
//struct sockaddr_in {
// __kernel_sa_family_t sin_family; /* Address family */
// __be16 sin_port; /* Port number */
// struct in_addr sin_addr; /* Internet address */
int main(int argc,char **argv)//传入ip 和端口
{
int c_fd;
int connect_fd;
char writeBuf[128] = {0};
char readBuf[128] = {0};
int n_read;
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
if(argc != 3){
perror("argc error");
}
c_fd = socket(AF_INET,SOCK_STREAM,0); // 1 创建网络返回套接字 ipv4 tcp 默认属性
if(c_fd == -1){
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));//字符串转整型 主机端口转网络短字节序
inet_aton(argv[1],&c_addr.sin_addr);//inet assica转网络字节序
connect_fd = connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr_in));
// 2 connect连接网络
if(connect_fd == -1){
perror("connect");
}
while(1){ //连接上后不断等待读取 并创建子进程写入数据
if(fork() == 0){//创建子进程来发送
while(1){
memset(writeBuf,0,sizeof(writeBuf));//先清空
printf("input: "); //提醒输入
gets(writeBuf);// 把输入的字符串放入到writeBuf中
write(c_fd,writeBuf,sizeof(writeBuf));// 4 发送内容
printf("\n");
}
}
memset(readBuf,0,sizeof(readBuf));//先清空
n_read = read(c_fd,readBuf,128); // 3 等待读取
if(n_read == -1){
perror("read");
}
else{
printf("client rcv:%d %s\n",n_read,readBuf);
}
}
return 0;
}