第11章下 与远端进程相连:socket

 管道使得进程向其他进程发送数据就像文件发送数据一样容易,但是管道具有两个重大的缺陷。管道在一个进程中被创建,通过fork实现共享。因此管道只能连接同一台主机上的进程。
 而socket允许在不相关的进程间创建类似管道的连接,甚至可以连接其他主机上的进程。

●socket编程重要概念
(1)客户和服务器
服务器是提供服务的程序,等待请求、处理请求、继续等待。客户端不需要循环,只需建立连接,与服务器交换数据,然后继续自己的工作。
(2)主机名和端口
运行在网上的服务器只是某台计算机上运行的一个进程,该计算机称为主机,进程拥有一个端口号。主机和端口的组合标识了一个服务器。/etc/services中定义了众所周知服务端口号的列表。
(3)协议
协议是服务器与客户交互的规则。如在时间服务器中,协议很简单,客户呼叫,服务器回答,给出时间信息后挂断。

●时间服务器的编写
在这里插入图片描述
(1)向内核申请一个socket
SOCK__STREAM类似于双向管道,数据作为连续的字节流从一端写入,再从另一端读出。还有SOCK_DGRAM类型。
protocol参数指的内核网络代码的协议,不是客户端服务器的协议,0代表标准协议
在这里插入图片描述
(2)绑定地址到socket上,地址=主机+端口号
bind调用将一个地址分配给socket,就像把电话号码分配给一个插座。
在这里插入图片描述
(3)在socket上允许接入呼叫并设置允许连接数为1
在这里插入图片描述
(4)等待/接收呼叫
 accept返回文件描述符,用该文件描述符进行读写操作,此文件描述符其实是与客户某个文件描述符的连接。
 如果后两个参数不为空的话,可以根据客户的地址来决定如何处理该连接。
在这里插入图片描述
(5)传输数据
 对从accept中获得的fd进行操作
(6)关闭连接
调用close关闭accept返回的文件描述符,一端关闭,另一端会得到文件结束标记,和管道类似。
 如果是远程ls服务器,就用popen打开ls,并从popen返回的FILE *中读取数据发送到服务器的输出fp

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<time.h>
#include<string.h>
#include<stdlib.h>

#define PORTNUM 13000 //端口号
#define HOSTLEN 256
#define oops(msg)	{perror(msg);exit(1);}
int main(int ac,char *av[])
{
	struct sockaddr_in saddr;	//地址结构体 ip+端口+协议
	struct hostent *hp;		//hostent(ry)结构体,主机的信息
	char hostname[HOSTLEN];	//主机名,通过主机名获得信息
	int sock_id,sock_fd;	//socket id ,文件描述符
	FILE *sock_fp;	//fdopen打开accpet返回的fd得到的FILE *
	time_t thetime;	//时间

	/*
	 *第一步:向内核申请socket
	 */
	sock_id=socket(PF_INET,SOCK_STREAM,0);
	if(sock_id==-1)
		oops("socket");

	/*
	 *第二步:绑定地址到socket 地址格式:主机+端口名
	 */
	bzero((void*)&saddr,sizeof(saddr));	//清空结构体
	gethostname(hostname,HOSTLEN);	//得到主机名
	hp=gethostbyname(hostname);	//根据主机名得到主机信息

	bcopy((void*)hp->h_addr,(void*)&saddr.sin_addr,hp->h_length);
		//将h_addr的地址复制到saddr中的sin_addr
	saddr.sin_port=htons(PORTNUM);	//填入端口号
	saddr.sin_family=AF_INET;	//填入协议簇
	if(bind(sock_id,(struct sockaddr *)&saddr,sizeof(saddr))!=0)
	//sockaddr中sa_data把目标地址和端口信息混在一起了。而sockaddr_in端口号和IP地址分开存储。bind接受的是sockaddr类型
		oops("bind");

	/*
	 *第三步:允许队列长度为1的呼叫
	 */
	if(listen(sock_id,1)!=0)
			oops("listen");

	/*
	 *主循环:accept(),write(),close();
	 */
	while(1){
		sock_fd=accept(sock_id,NULL,NULL);	//等待呼叫
			printf("Wow!got a call\n");
		if(sock_fd==-1)		
			oops("accept");
		sock_fp=fdopen(sock_fd,"w");	
			//fdopen打开呼叫进程文件描述符进行读写
		if(sock_fp==NULL)
			oops("fdopen");
		thetime=time(NULL);	//得到时间转为string
		fprintf(sock_fp,"The time here is..");
		fprintf(sock_fp,"%s",ctime(&thetime));
		fclose(sock_fp);
	}
}

●时间服务客户端的编写
在这里插入图片描述
(1)向内核请求建立socket
(2)与服务器相连
 如过connect成功,sock_id为一个合法的文件描述符,可以用来进行读写,写入该文件描述符的数据被发到另一端的socket,而另一端写入的数据可以从该文件描述符读取。
在这里插入图片描述
(3)传送数据
从sock_id中调用read读取数据
(4)挂断
关闭文件描述符并退出

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<stdlib.h>
#include<unistd.h>
#include<strings.h>
#define oops(msg) {perror(msg);exit(1);}

int main(int ac,char*av[])
{
	struct sockaddr_in servadd;		//要呼叫的服务器id
	struct hostent *hp;		//用来得到id
	int sock_id,sock_fd;
	char message[BUFSIZ];	//接收信息
	int messlen;	//信息长度

	/*
	 *第一步:获得一个socket
	 */
	sock_id=socket(AF_INET,SOCK_STREAM,0);
	if(sock_id==-1)
		oops("socket");

	/*
	 *第二步:连接服务器
	 *		需要先建立服务器地址(主机名+端口号)
	 */
	bzero(&servadd,sizeof(servadd));	//清空
	hp=gethostbyname(av[1]);	//寻找主机ip
	if(hp==NULL)
		oops(av[1]);
	bcopy(hp->h_addr,(struct sockaddr *)&servadd.sin_addr,hp->h_length);
	servadd.sin_port=htons(atoi(av[2]));	//加入端口号
	servadd.sin_family=AF_INET;		//加入协议

	if(connect(sock_id,(struct sockaddr *)&servadd,sizeof(servadd))!=0)
		oops("connect");

	/*
	 *第三步:从服务器获得数据,然后挂断
	 */
	messlen=read(sock_id,message,BUFSIZ);	
	//为什么服务器要用fdopen,这里却可以直接read
	if(messlen== -1)
		oops("read");
	if(write(1,message,messlen)!=messlen)	//写到stdout
		oops("write");
	close(sock_id);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值