目录
TCP/IP 是互联网的基础, TCP代表传输控制协议,IP代表互联网协议。目前有两个版本IP,一个是32位地址的IPv4 和一个是128位的 IPv6 。而IPv4 是现如今使用最多的IP版本,也是这次讨论的重点。
一、IP主机和IP地址
每一个注意由一个32位的IP地址来标识。为了方便起见,通常用32位的IP低质号用记点法标识例如:134.121.64.1 也可以用主机名标识如 dns1.eec.wsu.edu 。实际上应用程序通常使用主机名而不是IP地址。因为给定其中一个,我们都可以通过dns(域名系统)服务器找到另外一个,两者之间可以相互转换。
IP地址分为两部分 NeiworkID 和 HostID 字段。其中,IP 可以分为A~E类。例如B类IP分为一个16位NeiworkID,前两位是10 。发往UP地址的数据包首先被发送到具有相同networkID的路由器默认IP地址位127.0.0.1.
二、IP数据包格式
IP数据包由IP头、发送方IP地址、接收方IP地址和数据组成。每个IP数据包的大小最大为64K。IP头包含有关数据包的信息。内容如下:
IP主机可能距离很远通常不可能从一个主机直接向另一个主机发送数据包。路由器是转发数据包的特殊IP主机,它可以向普通IP主机和其他路由器发送数据包,
三、TCP/IP在网络中的数据流
应用层的数据被传到传输层会添加TCP 或者UDP包头来标识使用的传输协议。合并后的数据被传到IP网络层,添加一个包含IP地址的IP报头来标识发送和接收主机。然后合并后的数据传递到网络链路层,再次将数据分成多个帧,添加发送和接收网络的地址,用于在物理网络之间的传输。
四、套接字编程
服务端socket的就像插座,有n 个插孔可以对外提供服务。而服务逻辑由服务器内部实现,而客户端不必去关心。客户端socket就像插头,需要主动连接服务器才可以相互交流。而在计算机网络中,ip地址可以对应一台主机,而服务器主机上由多个在运行的程序,为了区分这些进程就必须通过接口来区分,同一个主机下的同一个接口只对应一个主机下的进程。
因此整个流程就是,
1.服务器就要创建一个Socket对象,而后用本机ip和接口与当前的程序进行绑定。开启监听查看是否有客户端请求连接。等待直到获取客户端的请求(对应一个文件描述符)
2.此时有客户端需要连接服务器,因此客户端首先要创建一个Socket对象指定要连接的具体内容,而后利用connect() 与服务器建立连接。
3.服务器监听到了客户端的请求,便可以利用 accept接受该客户端对应的连接进行通信,同时得到用于通信的文件描述符。
4.客户端与服务器不断请求和响应数据,直到客户端断开连接。
5.服务器端发现客户端的连接断开,服务器也跟着关闭通信的文件描述符。
6.客户端通信结束,服务器监听新的客户端请求。
在 netdb.h 和 sys/socket.h中有套接字的地址结构定义
struct sockaddr_in {
sa_family_t sin_family; //TCP/IP网络的sin_family 始终设置为AF_INET
in_port_t sin_port; //包含网络字节顺序排列的端口号
struct in_addr sin_addr ; //按网络字节顺序排列的IP地址
}
struct in_addr{
unit32_t s_addr; //按网络字节顺序排列的IP地址
}
服务器套接字编程步骤如下
- 创建socket;
- 绑定socket和端口号;
- 监听端口号; (UDP省略)
- 接收来自客户端的连接请求;(UDP省略)
- 从socket中读取字符;
- 发送消息回客户机。
客户端套接字编程步骤如下
- 创建socket;
- 连接指定计算机的端口; (UDP省略)
- 向socket中写入信息;
- 从服务器接收消息。
4.1 创建套接字
int socket(int domain, int type, int protocol);
domain参数 | 参数含义 |
---|---|
AF_INET | IPv4协议 |
AF_INET6 | Ipv6协议 |
AF_LOCAL | Unix协议域/只在本机内通信的套接字 |
AF_ROUTE | 路由套接字 |
AF_KEY | 秘钥套接字 |
type参数 | 参数含义 |
---|---|
SOCK_STREAM | 字节流套接字 (TCP) |
SOCK_DGRAM | 数据报套接字 (UDP) |
SOCK_SEQPACKET | 有序分组套接字 |
SOCK_RAW | 原始套接字 |
4.2绑定socket和端口号
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind系统调用将addr指定的地址分配文件描述符 sockfd所引用的套接字,addrlen指定addr所指向地址结构的大小对于用于联系其他UDP服务器主机的UDP套接字,必须绑定到客户机地址,允许服务器发回应答。对于接收客户机的TCP套接字,必须先将其绑定到服务器主机地址。
4.3、UDP 套接字
将缓冲区中len字节数据发送到dest_addr标识的目标主机
ssize_t sendto(int fd, const void *buf,size_t len,int flags,const struct sockaddr *dest_addr,socklen_t tolen);
从缓冲区中len字节数接收数据
ssize_t recvfrom(int fd, void *buf, size_t len,int flags,struct sockaddr *src_addr, socklen_t *len);
4.4 TCP 套接字
在绑定socket 和端口号后 TCP服务器用 listen 和 accpet 来监听、接受客户机的连接
//backlog 定义了等待连接的最大长度
int listen(int sockfd, int backlog);
//提取等待连接队列上的第一个连接请求同于监听sockfd,执行时造成进程阻塞,直到客户机通过connect建立连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); // 返回新的文件描述符
//如果sockfd 时SOCK_DGRAM (USD 套接字)类型时,addr时发送数据报的默认地址,也是接收数据报的唯一地址。如果时 SOCK_STREAM (TCP 套接字)类型时,connect连接到绑定到addr指定的套接字
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
发送/接收数据
可以使用 send/read 或者 recv/write 来接收和发送数据。
read 和 write 是对文件描述符的读取和写入
//对已经连接的fd 发送数据 ,flags 通常是0
ssize_t send(int fd , const void *buf , size_t len , int flags);
//对已经连接的fd 接收数据 ,flags 通常是0
ssize_t recv(int fd , void *buf , size_t len , int flags);
五、 UDP回显 服务器-客户机程序
服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFLEN 256
#define PORT 1234
char line[BUFLEN];
struct sockaddr_in me ,client;
int sock ,rlen=sizeof(client);
socklen_t clen = sizeof (client);
int main (){
printf("1. create a UDP socket\n");
sock = socket (AF_INET,SOCK_DGRAM,IPPROTO_UDP);
printf("2. fill me with server address and port number \n");
memset((char *) & me ,0,sizeof(me));
me.sin_family = AF_INET;
me.sin_port = htons(PORT);
me.sin_addr.s_addr = htonl(INADDR_ANY);
printf("3. bind socket to server IP and port \n");
bind (sock ,(struct sockaddr *) & me ,sizeof (me));
printf("4. wait for datagram \n");
while (1){
memset(line ,0,BUFLEN);
printf("UDP server:waiting for datagram \n");
rlen =recvfrom (sock , line , BUFLEN,0,(struct sockaddr *)&client ,&clen);
printf("received a datagram from [host : prot] =[%s :%d] \n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
printf("rlen=%d:line%s \n",rlen,line);
printf("send reply \n");
sendto (sock ,line,rlen,0,(struct sockaddr*)&client,clen);
printf("------------------------\n");
}
}
UDP客户机端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFLEN 256
#define SERVER_PORT 1234
#define SERVER_HOST "127.0.0.1"
char line[BUFLEN];
struct sockaddr_in server;
int sock ,rlen,slen = sizeof (server);
int main (){
printf("1. create a UDP socket\n");
sock = socket (AF_INET,SOCK_DGRAM,IPPROTO_UDP);
printf("2. fill in server address and port number \n");
memset((char *) & server ,0,sizeif(server));
server.sin_family = AF_INET;
server.sin_port = htons(SERVER_PORT);
inet_aton(SERVER_PORT , &server.sin_addr);
while(1){
printf("Enter a line: \n");
fget(line,BUFLEN,stdin);
line[strlen(line) -1 ]=0;
printf("send a line to server \n");
sendto(sock,line,strlen(len),0,(struct sockaddr *)&server,slen);
memset(line ,0 ,BUFLEN);
printf("try to receive a line from server \n");
rlen =recvfrom (sock,line,BUFLEN,0,(struct sockaddr *)&server,slen);
printf("rlen =%d : line=%s \n" ,rlen ,line);
}
}
六、TCP 回显服务器-客户机
服务器端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include<unistd.h>
#define MAX 256
#define SERVER_IP "127.0.0.1"
#define SERVER_HOST "localhost"
#define SERVER_PORT 1234
struct sockaddr_in server_addr ,client_addr;
int mysock,csock; //sock的文件描述符
int r,n; //辅助变量
socklen_t len;
int server_init(){
printf("=============server init========\n");
printf("1. create a TCP socket\n");
mysock = socket (AF_INET,SOCK_STREAM,0);
if(mysock < 0){
printf("socket call failed\n"); exit(1);
}
printf("2. fill server_addr with host_ip and port number \n");
server_addr .sin_family = AF_INET;
server_addr .sin_port = htons(SERVER_PORT);
server_addr .sin_addr.s_addr = htonl(INADDR_ANY);
printf("3. bind socket to server address \n");
r =bind (mysock ,(struct sockaddr *) & server_addr ,sizeof (server_addr ));
if( r<0){
printf("bind call failed\n"); exit(3);
}
printf(" hostname =%s, port = %d \n", SERVER_HOST ,SERVER_PORT);
listen(mysock , 5 );
printf("============init done ===========\n");
return 0;
}
int main (){
char line [MAX];
server_init();
while (1){
printf("server accepting new connection... \n");
len = sizeof(client_addr);
csock = accept(mysock,(struct sockaddr *)&client_addr,&len);
if(csock <0){
printf("accept failed\n"); exit(1);
}
printf("Server: accept a client :IP=%s , port = %d \n",
inet_ntoa(client_addr.sin_addr) ,
ntohs(client_addr.sin_port));
while(1){
n=read(csock,line,MAX);
if(n=0) {
printf("clinet dead ,server loop\n");
close(csock); break;
}
printf("read n =%d byte;line = %s \n", n,line);
n=write(csock,line,MAX);
printf("write n =%d byte;ECHO = %s \n", n,line);
printf("ready for next request\n");
}
}
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<unistd.h>
#define MAX 256
#define SERVER_HOST "localhost"
#define SERVER_PORT 1234
struct sockaddr_in server_addr ;
int sock,r;
int client_init(){
printf("=============client init========\n");
printf("1. create a TCP socket\n");
sock = socket (AF_INET,SOCK_STREAM,0);
if(sock < 0){
printf("socket call failed\n"); exit(1);
}
printf("2. fill server_addr with host_ip and port number \n");
server_addr .sin_family = AF_INET;
server_addr .sin_port = htons(SERVER_PORT);
server_addr .sin_addr.s_addr = htonl(INADDR_ANY);
printf("3. connecting to server \n");
r = connect (sock ,(struct sockaddr *) & server_addr ,sizeof (server_addr ));
if( r<0){
printf("bind call failed\n"); exit(3);
}
printf(" hostname =%s, port = %d \n", SERVER_HOST ,SERVER_PORT);
printf("============client init done ===========\n");
return 0;
}
int main (){
char line [MAX] , ans[MAX];
int n;
client_init();
printf(" ********processing loop *******************");
while (1){
printf("put a line... \n");
bzero(line ,MAX);
fgets(line,MAX,stdin);
line[strlen(line) - 1] = 0;
if(line[0] ==0) exit(0);
n=write(sock,line,MAX);
printf("client : wrote n =%d bytes ; line %s :\n", n , line);
n =read(sock ,ans,MAX);
printf("client : read n =%d bytes ; line %s :\n", n , line);
}
}