一、引言
在进程间通信中了解了各种UNIX系统所提供的经典进程间通信机制(IPC)、管道、FIFO、消息队列、信号量以及共享内存。这些机制允许在同一台计算机上运行的进程可以相互通信。与其不同的是,网络进程通信(network IPC)是不同计算机(通过网络连接)上的进程相互通信的机制。 我们将描述套接字网络进程间通信接口,进程用该接口能够和其他进程通信,无论它们是在同一台计算机还是在不同的计算机上。实际上,这正是该套接字接口的设计目标之一:同样的接口既可以用于计算机间通信,也可以用于计算机内通信。尽管套接字接口可以采用许多不同的网络协议进行通信,但在这篇文章我们的讨论限制在因特网事实上的通信标准:TCP/IP协议栈。(一般需要用到TCP协议和UDP协议)
二、TCP/UDP对比
1.TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前 不需 要建立连接。 2.TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付 3.TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的。 4.UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低。(对实时应用很有用,如IP电话,实5.时视频会议等) 6.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。 7.TCP首部开销20字节;UDP的首部开销小,只有8个字节。 8.TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
在使用套接字之前,需要知道如何标识目标通信进程。进程标识由两部分组成。一部分是计算机的网络地址,它可以帮助标识网络上我们想与之通信的计算机;另一部分是该计算机上用端口号表示的服务,它可以帮助标识特定的进程。
三、端口号作用
一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等
这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。
实际上是通过“IP地址+端口号”来区 分不同的服务的。 端口提供了一种访问通道, 服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。
四、字节序
1.概述:字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。 2.常见序有小端字节序(Little endian)和大端字节序(Big endian)。 ①Little endian:将低序字节存储在起始地址。 ②Big endian:将高序字节存储在起始地址。 网络字节序=大端字节序 使用字节序的原因: 计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。在计算机内部,小端序被广泛应用于现代 CPU 内部存储数据;而在其他场景,比如网络传输和文件存储则使用大端序。
五、socket服务器和客户端的开发步骤
1.创建套接字 2.为套接字添加信息(IP地址和端口号) 3.监听网络连接 4.监听到有客户接入,接受一个连接 5.数据交换 6.关闭套接字,断开连接
六、Linux提供的API简析
1.套接字是通信端点的抽象。正如使用文件描述符访问文件,应用程序用套接字描述符访问套接字。套接字描述符在UNIX系统中被当作是一种文件描述符。事实上,许多处理文件描述符的函数(如read和write)可以用于处理套接字描述符。
函数socket
为创建一个套接字,调用socket函数(连接协议)
NAME
socket - create an endpoint for communication
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
*domain:指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族): ·AF INET IPv4因特网域 ·Ar_INETS IPv6因特网城 ·AF UNIX Unix域 ·Al ROUIE 路山套接字 ·AF_KEY密钥套接字 ·AF UMPSPEC 未指定
*type参数指定socket的类型: ·SOCK_STREAM:流式会接字提供可靠的、面问连按的通信流;它使用TCP协议,从而呆证了数据传愉的正确性私顺字性 ·SOCK_DGRAM:数据报会接子定义了种无连接的服 。数据通过相互独立的振义进行传愉,是无序的,并且不得证是可靠、无差错的。它使用数据报协议UDP。 ·SOCK_RAW:允许程序使用低层协议,原始自孩字允计对底层协议如IP成ICNP进行直按访问,边能很大但使用领为不便,主要用丁一些办议的开发。
*protocol:通常赋信”0”。 ·0选择type类型对应的国认协议 ·IPPROTO_TCP TCP传销协议 ·IPPROTO_UDP UDP 传轴办议 ·IPPROTO_SCTP SCTP传偷协议 ·IPPROTO_TIPC TIPC传输协议
函数bind
2.将一个客户端的套接字关联上一个地址没有多少新意,可以让系统选一个默认的地址。然而,对于服务器,需要给一个接收客户端请求的服务器套接字关联上一个众所周知的地址。客户端应有一种方法来发现连接服务器所需要的地址,最简单的方法就是服务器保留一个地址并且注册在/etc/services或者某个名字服务中。
使用bind函数来关联地址和套接字。(将套接字与地址关联)
NAME
bind - bind a name to a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t len);
①功能:用于绑定IP地址和端口号到skcketfd。 参数: *sockfd:是一个socket描述符 *addr:是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同。
struct sockaddr {
unisgned short sa_family;
char sa_data[14];
};
同等替换:
struct sockaddr_in {
sa_family_t sa_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
地址转换API
①int inet_aton(const char* straddr,struct in_addr *addrp);
把字符串形式的“192.168.1.123”转为网络能识别的格式
②char* inet_ntoa(struct in_addr inaddr);
把网络格式的ip地址转为字符串形式
注:查找struct sockaddr_in过程 i不区分大小写,r递归,n显示行号 *在当前目录底下 下图指令可以看到bind()中的参数addr。 下图中的sin_port则需要用到htons函数。 通过下面指令可以查看in_addr的结构体: *参数addrlen表示addr结构的长度,可以用sizeof操作符获得。
函数listen
3.服务器调用listen函数来宣告它愿意接收连接请求
NAME
listen - listen for connections on a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
①功能: ·设置能处理的最大连接数,listen()并未开始接受连线,只是设置sockect的listen模式, listen函数只用于服务器端,服务器进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接, 然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将一个未连接的套接字转换为一个被动套接字(监听) ,规定内核为相应套接字排队的最大连接数。 ·内核为任何一个给定监听套接字维护两个队列: ·未完成连接队列,每个这样的SYN报文段对应其中-项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程。这些套接字处于SYN_ REVD 状态; ·已完成连接队列,每个已完成TCP三次握手过程的客户端对应其中-项。这些套接字处于ESTABLISHED 状态; ②参数: *sockfd:是socket系统调用返回的服务器端socket描述符。 *backlog:指定在请求队列中允许的最大请求数。
函数accept
4.连接:一旦服务器调用了listen,所用的套接字就能接收连接请求。使用accept函数获得连接请求并建立连接。
NAME
accept - accept a connection on a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *restrict addr,
socklen_t *restrict len);
①功能:accept 函数由TCP服务器调用, 用于从已完成连接队列队头返回下一 个已完成连接。如果已完成连接队列为空, 那么进程被投入睡眠。 ②参数 ·sockfd:sockfd是socket系统调用返回的服务器端socket描述符。 ·addr:用来返回已连接的对端(客户端)的协议地址。 · addrled:客户端地址长度。 ③返回值 ·该函数的返回值是一个 新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个 参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字, 它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示 TCP三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。 如果没有连接请求在等待,accept会阻塞等待一个请求的到来。
函数write和read
5.数据收发 字节流读取函数:在套接字通信中进行字节读取函数 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);
①第一个将buf中的nbytes个字节写入到文件描述符fd中,成功时返回写的字节数。 ②第二个为从fd中读取nbyte个字节到buf中,返回实际所读的字节。 详细应用说明参考使用read,write读写socket(套接字)。 网络I/O还有一些函数,例如:recv()/send(),readv()/writev(),recvmsg()/sendmsg(),recvfrom()/sendto()等。
函数send和recv
6.数据收发常用第二套API ①在TCP套接字上发送数据函数:有连接
ssize_t send(int s,const void *msg,size_t len,int flags);
函数只能对处于连接状态的套接字使用, *参数s为建立好连接的套接字描述符,即accept函数的返回值 *参数msg指向存放待发送数据的缓冲区 *参数len为待发送数据的长度,参数flags为控制选项,一般设置为0
②在TCP套接字上接收数据函数:有连接
ssize_t recv(int s,void *buf,size_t len,int flags);
*函数recv从参数s所指定的套接字描述符(必须是面向连接的套接字)上接收 *数据并保存到参数buf所指定的缓存区 *参数len则为缓冲区长度,参数flags为控制选项,一般设置为0
七、建立连接
函数connect
如果要处理一个面向连接的网络服务(SOCK_STREAM或 SOCK_SEQPACKET),那么在开始交换数据以前,需要在请求服务的进程套接字(客户端)和提供服务的进程套接字(服务器)之间建立一个连接。使用connect函数来建立连接:
NAME
connect - initiate a connection on a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
①功能:该函数用于绑定之后的client端(客户端),与服务器建立连接 ②参数: *sockfd:是目的服务器的socket描述符 *addr:是服务器的IP地址和端口号的地址结构指针 *addrlen :地址长度被设置为sizeof(struct sockaddr)
八、字节序转换API
#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代表net,s代表short(两个字节),l代表long(四个字节),通过上面的四个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INDDR_ANY,INADDR_ANY指定地址让操作系统自己获取。
实例1:用socket实现客户端和服务端连接
服务端代码:serve.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int s_fd;
int n_read;
char readbuf[128];
char *msg="i get your connect";
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));
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd==-1){
perror("socket");
exit(-1);
}
s_addr.sin_family =AF_INET;
s_addr.sin_port =htons(8989);
inet_aton("127.0.0.1",&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
listen(s_fd,10);
int clen=sizeof(struct sockaddr_in);
int c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd==-1){
perror("accept");
}
printf("get connetc:%s\n",inet_ntoa(c_addr.sin_addr));
n_read=read(c_fd,readbuf,128);
if(n_read==-1){
perror("read");
}else{
printf("get message:%d,%s\n",n_read,readbuf);
}
write(c_fd,msg,strlen(msg));
return 0;
}
客户端代码:cline.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int c_fd;
int n_read;
char readbuf[128];
char *msg = "msg from client";
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){
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(8989);
inet_aton("127.0.0.1",&c_addr.sin_addr);
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(-1);
}
write(c_fd,msg,strlen(msg));
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;
}
编译运行serve.c,服务端运行中: 编译运行cline.c与服务端连接,接收到服务器的数据 服务器接收到客户端数据
实例2:用socket实现双方聊天
chat_s.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int s_fd;
int c_fd;
int n_read;
char readbuf[128];
char msg[128] = {0};
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
if(argc != 3) {
printf("param is error\n");
exit(-1);
}
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd==-1){
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
5000-9000 htons()生成网络字节序
inet_aton(argv[1],&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
listen(s_fd,10);
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("get connetc:%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);
write(c_fd,msg,strlen(msg));
}
}
while(1) {
memset(msg,0,sizeof(readbuf));
n_read = read(c_fd,readbuf,128);
if(n_read == -1){
perror("read");
}else{
printf("get message:%d,%s\n",n_read,readbuf);
}
}
break;
}
}
return 0;
}
chat_c.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int c_fd;
int n_read;
char readbuf[128];
char msg[128] = {0};
struct sockaddr_in c_addr;
if(argc != 3) {
printf("param is error\n");
exit(-1);
}
memset(&c_addr,0,sizeof(struct sockaddr_in));
c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));
5000-9000 htons()生成网络字节序
inet_aton(argv[1],&c_addr.sin_addr);
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(-1);
}
while(1) {
if(fork() == 0) {
while(1) {
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
while(1) {
memset(msg,0,sizeof(readbuf));
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;
}
先编译运行chat_s.c,服务端先运行,再编译运行客户端,即可进行双方聊天
实例3:多方接入服务端
服务端:serve.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int s_fd;
int c_fd;
int n_read;
char readbuf[128];
char msg[128] = {0};
int mark = 0;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
if(argc != 3) {
printf("param is error\n");
exit(-1);
}
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd==-1){
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
用户一般用5000-9000 htons()生成网络字节序
inet_aton(argv[1],&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
listen(s_fd,10);
int clen=sizeof(struct sockaddr_in);
while(1) {
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
}
mark++;
printf("get connetc:%s\n",inet_ntoa(c_addr.sin_addr));
if(fork() == 0) {
if(fork() == 0) {
while(1) {
sprintf(msg,"welcome NO.%d cline",mark);
write(c_fd,msg,strlen(msg));
sleep(3);
}
}
while(1) {
memset(msg,0,sizeof(readbuf));
n_read = read(c_fd,readbuf,128);
if(n_read == -1){
perror("read");
}else{
printf("get message:%d,%s\n",n_read,readbuf);
}
}
break;
}
}
return 0;
}
客户端:chat_c.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int c_fd;
int n_read;
char readbuf[128];
char msg[128] = {0};
struct sockaddr_in c_addr;
if(argc != 3) {
printf("param is error\n");
exit(-1);
}
memset(&c_addr,0,sizeof(struct sockaddr_in));
c_fd = socket(AF_INET,SOCK_STREAM,0);
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);
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(-1);
}
while(1) {
if(fork() == 0) {
while(1) {
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
while(1) {
memset(msg,0,sizeof(readbuf));
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;
}
|