一、有关概述
1.TCP UDP 对比:
1. TCP面向连接(如打电话要先拨号建立连接)
UDP 是无连接的 即发送数据之前,不需要建立连接
2. TCP 提供可靠的服务,(通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达)
UDP尽最大努力交付,不保证可靠交付
3. TCP 面向字节流,实际上是TCP把数据看成一连串无结构的字节流
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
二、字节序
1.概述
字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序
2.常见序
1. Little endian:将低序字节存储在起始地址 小端字节序
2. Big endian:将高序字节存储在起始地址 大端字节序
网络字节序: 大端字节序
x86系列CPU都是little-endian的字节序.
例子:在内存中双字0x01020304(DWORD)的存储方式
内存地址
4000&4001&4002&4003
LE 04 03 02 01
BE 01 02 03 04
例子:如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
地址 | 大端 小端 |
---|---|
0x0000 | 0x12 0xcd |
0x0001 | 0x34 0xab |
0x0002 | 0xab 0x34 |
0x0003 | 0xcd 0x12 |
3.端口号的转换
htons 把unsigned short类型从主机序转换到网络序
htonl 把unsigned long类型从主机序转换到网络序
ntohs 把unsigned short类型从网络序转换到主机序
ntohl 把unsigned long类型从网络序转换到主机序
三、socket编程步骤
说明
1.创建套接字
2.为套接字添加信息(IP地址 端口号)
3.监听网络连接
4.监听有客户端接入,接受一个连接
5.数据交互
6.关闭套接字,断开连接
四、有关API简析
1. 创建套接字
int socket(int domain, int type, int protocol);
//返回一个网络描述符(类比文件描述符)
(1)domain: 所使用的协议族,通常为AF_INET(IPv4因特网域),表示互联网协议族(TCP/IP协议族)
(2)type : 指定socket的类型
SOCK_STREAM
流式套接字 使用TCP协议
SOCK_DGRAM
数据报套接字 使用UDP
(3)protocol: 通常赋值为0
0选择type类型对应的默认协议
2.关联地址
int bind(int sockfd, const struct sockaddr *addr ,socklen_t addrlen);
(1)sockfd:一个socket描述符
(2)addr: 一个指向包含本机IP地址及端口号等信息的sockaddr类型的指针
IPv4对应的是:
struct sockaddr {
sa_family_t sa_family; 协议族
char sa_data[14]; IP+端口
}
同等替换:
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */ 协议族
__be16 sin_port; /* Port number */ 端口号
struct in_addr sin_addr; /* Internet address */ IP地址结构体
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK _SIZE__ - sizeof(short int) - 填充
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
(3)addrlen:结构体长度
3.地址转换:
(1) 把字符串形式的“192.168.1.123”转为网络能识别的格式
int inet_aton(const char* straddr, struct in_addr*addrp);
(2)把网络格式的IP地址转为字符串形式
char* inet_ntoa(struct in_addr inaddr);
4.监听
int listen(int sockfd, int backlog);
内核为任何一个给定监听套接字维护两个队列
未完成队列
已完成队列
backlog:指定在请求队列中允许的最大请求数
5. 等待接收
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:accept函数由 TCP 服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。
参数:
(1)sockfd 是socket 系统调用返回的服务器端socket描述符
(**2) addr 返回已经连接的对端(客户端)的协议地址
(3)addlen 客户端地址长度
返回值是一个新的套接字描述符 表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示TCP三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。
6.接受和发送数据
(1)接受数据
ssize_t read(int fd, void *buf, size_t count); //跟读数据到文件操作一样
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
(2)发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t write(int fd, const void *buf, size_t count); //跟写数据到文件一样操作
7.客户端 connect
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
(1)addr: 要连接的套接字的IP地址和端口号的地址结构指针
(2)addrlen : 地址长度常被设置为sizeof(struct sockaddr)
五、服务端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main()
{
int s_fd;
int c_fd;
char readbuf[128] = {0};
char *mes = "welcome to you!";
struct sockaddr_in s_addr; // about serve in "bind"
struct sockaddr_in c_addr; // about client in "accept"
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){
printf("socket error\n");
exit(-1);
}
//int bind(int sockfd, const struct sockaddr *addr,
// socklen_t addrlen);
s_addr.sin_family = AF_INET; //Internet
s_addr.sin_port = htons(8989); //port
inet_aton("10.164.125.59",&s_addr.sin_addr); //address
//2.bind
bind(s_fd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
//4.accept
int c_addrlen = sizeof(struct sockaddr_in);
c_fd = accept(s_fd,(struct sockaddr*)&c_addr,&c_addrlen);
if(c_fd == -1){
perror("accept");
}
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
int n_read = read(c_fd,readbuf,128);
if(n_read == -1){
perror("read");
}else {
printf("get message from client: %d %s\n",n_read,readbuf);
}
int n_write = write(c_fd,mes,strlen(mes));
if(n_write == -1){
perror("write");
}else{
printf("write %d byte ok\n",n_write);
}
return 0;
}
六、客户端
//client
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char **argv)
{
if(argc != 3){
printf("param error\n");
exit(-1);
}
int c_fd;
char readbuf[128] = {0};
char *buf = "hello serve!";
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
printf("socket error\n");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));
inet_aton((argv[1]),&c_addr.sin_addr);
if( connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr_in)) == -1){
printf("connect error\n");
}
int n_write = write(c_fd,buf,strlen(buf));
if(n_write == -1){
perror("write");
}else{
printf("write %d byte ok\n",n_write);
}
int n_read = read(c_fd,readbuf,128);
if(n_read == -1){
perror("read");
}else {
printf("get message from serve: %d %s\n",n_read,readbuf);
}
return 0;
}