一、IP地址和端口号
1、IP地址
IP协议有两个版本, IPv4和IPv6。
IP地址是在IP协议中, 用来标识网络中不同主机的地址;
对于IPv4来说, IP地址是一个4字节, 32位的整数; 我们通常也使用 "点分十进制" 的字符串表示IP地址, 例如 192.168.0.1 ;
用点分割的每一个数字表示一个字节, 范围是 0 - 255。
在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址.
但是,我们只有IP地址是不能完成通信的。
比如我们给别人发消息, 有了IP地址能够把消息发送到对方的机器上, 但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析.
2、端口号
端口号(port)是传输层协议的内容。
端口号是一个2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
一个端口号只能被一个进程占用。
传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要发给谁";
注意:IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
二、认识传输层协议
1、TCP协议
传输层协议;
有连接;
可靠传输;
面向字节流
2、UDP协议
传输层协议;
无连接 ;
不可靠传输;
面向数据
三、网络字节序
我们已经了解过,内存中的多字节数据存储时,相对于内存地址有大端和小端之分
磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流当然也同样有大端小端之分. 那么如何定义网络数据流的地址呢?如下规定:
1、发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
2、接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:
先发出的数据是低地址,后发出的数据是高地址.
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
所以, 如果当前发送主机是小端, 我们就必须先将数据转成大端; 否则就忽略, 直接发送。
这些,由系统完成,有相应的
网络字节序和主机字节序的转换函数,来了解一下:
观察上图的函数名,很容易理解:
h(host),n(network),l(long),s(short)
举例说明:
htonl:表示将32位的长整数从主机字节序转换为网络字节序
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,忽略,直接返回。
四、socket编程接口
了解完这些基础知识,我们来看一下socket编程需要用到的接口。
1、sockaddr结构体
适用于各种底层网络协议,能够用一套接口实现任意一种情况的网络编程,类似于泛型的一种结构体,用前两个字节辨识是哪一个结构体。
比如:IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位IP地址. IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6.
这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
2、socket 常见API
创建 socket 文件描述符:
参数:domain表示域,当前要使用的协议簇;type表明使用哪种服务(tcp或udp);protocol设置为0
返回值:
成功返回文件描述符
错误返回-1
绑定端口号:
返回值:
接收数据:
回消息:
五、简单的UDP网络程序
1、首先我们写好Makefile文件:
PHONY:all
all:udp_server udp_client
udp_server:udp_server.c
gcc -o $@ $^
udp_client:udp_client.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f udp_client udp_server
2、udp_server.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int startup(const char *ip,int port){
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0){
printf("socket error!\n");
exit(1);
}
printf("%d\n",sock);
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=inet_addr(ip);
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
printf("bind error!\n");
exit(2);
}
return sock;
}
void service(int sock){
char buf[1024];
struct sockaddr_in client;
while(1){
socklen_t len=sizeof(client);
ssize_t s=recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&clien t,&len);
if(s>0){
buf[s]=0;
printf("[%s:%d]:%s\n",inet_ntoa(client.sin_addr),ntohs(client.si n_port),buf);
sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&client,len);
}
}
}
static void usage(const char *proc){
printf("Usage: %s [ip] [port]\n",proc);
}
int main(int argc,char *argv[]){
if(argc!=3){
usage(argv[0]);
return 1;
}
int sock=startup(argv[1],atoi(argv[2]));//"8080"
service(sock);
return 0;
}
3、udp_client.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main(int argc,char *argv[]){
if(argc!=3){
printf("Usage: %s ip\n",argv[1]);
return 1;
}
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0){
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(atoi(argv[2]));
server.sin_addr.s_addr=inet_addr(argv[1]);
char buf[1024];
struct sockaddr_in peer;
while(1){
socklen_t len=sizeof(peer);
printf("Please Enter# ");
fflush(stdout);
ssize_t s=read(0,buf,sizeof(buf)-1);
if(s>0){
buf[s-1]=0;
sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&server,\
sizeof(server));
ssize_t _s=recvfrom(sock,buf,sizeof(buf)-1,0,\
(struct sockaddr*)&peer,&len);
if(_s>0){
buf[_s]=0;
printf("server echo# %s\n",buf);
}
}
}
close(sock);
return 0;
}
欢迎大家指正不足之处。