服务器和客户端之间要实现通讯,先规定好一个协议。
要重视协议这个东西,日后开发肯定会用到,有时间好好学一下;
1、UNIX域?
TCP/IP协议,可以用于不同计算机之间的通讯,使用对应的IP和端口来找到对方,其实是一种进程间通讯,那能否在同一个电脑上面
不同进程间进行通讯?答案肯定是可以的,UNIX域是一个结合了套接字和管道通讯的一种进程间通讯的方式。它也有UDP/TCP的通讯的
方式,这两种方式都是可靠的,都不会造成数据的丢失,或者数据发送顺序的混乱。
在电脑本地实现进程间通讯的一种套接字。
2、无名UNIX域
无名:有亲缘关系的进程间通讯。
有名:文件系统中,有一个文件或者节点,两个不同进程通过打开这个文件或者节点实现进程间通讯。
创建一个无名UNIX域:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socketpair(int domain, int type ,int protocol, int sv[2]);
第一个参数:表示所需要使用的协议族,AF_UNIX, AF_LOCAL 建立UNIX的协议
第二个参数:表示创建套接字的类型:SOCK_STREAM (流式的套接字) SOCK_DGRAM(数据报的套接字),这两种通讯方式
因为是使用在本机上的父子进程间通讯,所以都是可靠的。
第三个参数:表示类型,这里都是用0。
第四个参数:用来保存通讯所用到的socket的文件描述符。这种通讯是双工,在使用之前先将一端关闭,使用另外一端来通讯。
返回值:成功返回0
失败返回-1
示例代码:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
int main()
{
//声明这两个fd用于父子进程间通讯
int fds[2];
char wbuf[12]="0123456789";
char rbuf[12]={0};
int ret;
int pid;
//创建本地域的套接字
ret = socketpair(AF_UNIX, SOCK_STREAM,0,fds);
if(ret < 0)
{
perror("socketpair fail");
return -1;
}
//创建子进程
pid = fork();
printf("pid=%d,getpid=%d,getppid=%d\n",pid,getpid(),getppid());//看下面的打印现象;
if(pid < 0)
{
perror("fork fail");
return -1;
}
else if(pid == 0)
{
//在子进程里面先写一个字符串给父进程
close(fds[0]);
write(fds[1],wbuf,12);
//再从父进程读一个字符串
read(fds[1],rbuf,12);
printf("child read from parent :%s\n",rbuf);
}else
{
//先关闭其中一个端口
close(fds[1]);
//先读子进程的数据
read(fds[0],rbuf,12);
printf("parent read from child :%s\n",rbuf);
memset(wbuf,0,12);
strncpy(wbuf,"xiejianming",strlen("xiejianming"));
//再把另外一个信息写给子进程
write(fds[0],wbuf,strlen("xiejianming"));
waitpid(pid,NULL,0);
}
return 0;
}
pid=3781,getpid=3780,getppid=2452
pid=0,getpid=3781,getppid=3780//对照理解子进程,父进程;
3、有名UNIX域
1)流式的套接字 (TCP)
(1)、客户端
A、创建套接字 socket
int clientfd = socket( AF_UNIX, SOCK_STREAM, 0 );
B、初始化地址结构体
通用地址结构体
struct sockaddr {
sa_family_t sa_family; //指定所用的协议族。
char sa_data[14]; //这里是具体的地址填充的空间。
}
对于UNIX域来说,它也有自己的地址结构体,这个地址结构体在<linux/un.h>
#define UNIX_PATH_MAX 108
struct sockaddr_un {
__kernel_sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */ 有名UNIX域,这个节点在文件系统哪里 可以是随意的一个文件,和有名管道差不多,这个结点不能建在share文件下面,windows系统下面没有这种节点机制;
(不能在共享目录下创建这个节点!!!!!)
};
C、使用连接函数连接服务器 connect
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
第一个参数:就是第一步新建的socket返回的fd。
第二个参数:指定你要链接的服务器的地址,就是第二步创建的地址。
第三个参数:这个地址的长度。
返回值:
如果成功返回0;
如果失败返回-1;
D、连接服务器成功之后,就可以使用读写函数去通讯
对数据收发:read/recv write/send.
E、关闭
关闭:close
(2)、服务器
A、创建套接字 socket
int clientfd = socket( AF_UNIX, SOCK_STREAM, 0 );
B、初始化地址结构体
通用地址结构体
struct sockaddr {
sa_family_t sa_family; //指定所用的协议族。
char sa_data[14]; //这里是具体的地址填充的空间。
}
对于UNIX域来说,它也有自己的地址结构体,这个地址结构体在<linux/un.h>
#define UNIX_PATH_MAX 108
struct sockaddr_un {
__kernel_sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */ 有名UNIX域,这个节点在文件系统哪里
(不能在共享目录下创建这个节点!!!!!)
};
C、绑定 bind
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
第一个参数:就是前面返回的文件描述符。
第二个参数:指定我们通讯的地址,这个是一个通用的结构体,它的地址,由指定的协议族去决定的。
第三个参数:指定的是第二个参数地址的大小。
返回值:
如果成功返回0;
如果失败返回-1;
D、监听:listen
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
第一个参数:前面新建的fd。
第二个参数:监听队列上最大的请求数。
返回值:
如果成功返回0;
如果失败返回-1;
E、接受链接:accept。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
第一个参数:前面新建的fd。
第二个参数:用来保存客户端的地址信息。
第三个参数:用来保存客户端地址的长度。
返回值:成功的话返回一个新的fd,然后服务器就可以通过这个新的fd和客户端进行通讯。
如果失败返回 -1;
F、对数据收发:read、recv ,发送:write、send。
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
第一个参数:是前面accept函数返回的新的fd。代表我们要和对应的客户端进行通讯。
第二个参数:是我们要发送或者接收的buffer对应的地址。
第三个参数:对应是我们要读取或者写入的数据按字节计算的长度。
返回值:成功的话,返回值的大小代表实际读取或者写入的字节数。这个返回值,和count不一定相等。
如果出错的话,返回-1;
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
第一个参数:是前面accept函数返回的新的fd。代表我们要和对应的客户端进行通讯。
第二个参数:是我们要发送或者接收的buffer对应的地址。
第三个参数:对应是我们要读取或者写入的数据按字节计算的长度。
第四个参数:一般都是0;//send,recv函数比write,read函数就是多了一个参数0;
返回值:成功的话,返回值的大小代表实际读取或者写入的字节数。这个返回值,和count不一定相等。
如果出错的话,返回-1;
G、关闭:close
TCP服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/un.h>
#include <sys/types.h>
#include <sys/socket.h>
int main()
{
// 第一步,创建这个UNIX域套接字
int serverfd = socket( AF_UNIX, SOCK_STREAM, 0 );
//第二步,声明地址结构体,并且初始化
struct sockaddr_un serveraddr,clientaddr;
int ret;
memset(&serveraddr,0,sizeof(serveraddr));
memset(&clientaddr,0,sizeof(clientaddr));
serveraddr.sun_family = AF_UNIX;
//注意这个路径一定要存在的,并且不能放在共享目录下面
strcpy(serveraddr.sun_path,"/tmp/unix");//可以是随意路径的文件,程序运行后能查到这些节点,系统创建的
unlink("/tmp/unix");//删除
//第三步,绑定服务器的地址
ret = bind(serverfd, (struct sockaddr *)&serveraddr,sizeof(serveraddr) );
if(ret < 0)
{
perror("server bind fail ");
close(serverfd);
return -1;
}
//第四步,监听服务器的fd,看是否有连接进来
ret = listen(serverfd,5);
if(ret < 0)
{
perror("listen bind fail ");
close(serverfd);
return -1;
}
int size = sizeof(clientaddr);
//第五步,如果有连接进来,就接收它
int clientfd = accept(serverfd,(struct sockaddr *)&clientaddr, &size);//第二个参数存储客户端的地址信息
char buf[128];
while(1)
{
//第六步,就可以互相通讯了。
bzero(buf,128);
read(clientfd,buf,128);
printf("Read from client:%s\n",buf);
printf("Please input a string:\n");
bzero(buf,128);
fgets(buf,128,stdin);
write(clientfd,buf,128);
}
//第七步,通讯结束后,关闭对应的文件描述符。
close(clientfd);
close(serverfd);
}
Tcp客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/un.h>
#include <sys/types.h>
#include <sys/socket.h>
int main()
{
// 第一步,创建这个UNIX域套接字
int clientfd = socket( AF_UNIX, SOCK_STREAM, 0 );
//第二步,声明地址结构体,并且初始化
struct sockaddr_un serveraddr;
int ret;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sun_family = AF_UNIX;
strcpy(serveraddr.sun_path,"/tmp/unix");
//第三步,连接服务器
ret = connect(clientfd,( struct sockaddr * )&serveraddr, sizeof(serveraddr));//第二个参数里面有地址信息;
if(ret < 0)
{
perror("client connect server fail");
close(clientfd);
return -1;
}
char buf[128];
while(1)
{
//第四步,进行读写操作//tcp通过客户端套接字
bzero(buf,128);
printf("Please input a string:\n");
fgets(buf,128,stdin);
write(clientfd,buf,128);
bzero(buf,128);
read(clientfd,buf,128);
printf("Read from server:%s\n",buf);
}
//第五步,关闭文件描述符
close(clientfd);
}
2)UNIX域 UDP通讯方式
(A)、服务器
(1)、创建socket
int serverfd = socket( AF_UNIX, SOCK_DGRAM,0 );
(2)、配置好自己的地址结构体。
初始化地址结构体
通用地址结构体
struct sockaddr {
sa_family_t sa_family; //指定所用的协议族。
char sa_data[14]; //这里是具体的地址填充的空间。
}
对于UNIX域来说,它也有自己的地址结构体,这个地址结构体在<linux/un.h>
#define UNIX_PATH_MAX 108
struct sockaddr_un {
__kernel_sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */ 有名UNIX域,这个节点在文件系统哪里
(不能在共享目录下创建这个节点!!!!!)
};
(3)、绑定自己的地址结构体。 bind
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
第一个参数:就是前面返回的文件描述符。
第二个参数:指定我们通讯的地址,这个是一个通用的结构体,它的地址,由指定的协议族去决定的。
第三个参数:指定的是第二个参数地址的大小。
返回值:
如果成功返回0;
如果失败返回-1;
(4)、等待客户端发信息给服务器。(服务器是被动接收数据)
等待客户端发信息过来 recvfrom
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
第一个参数:是前面socket函数返回的fd。代表我们要和对应的客户端进行通讯。
第二个参数:是我们要发送或者接收的buffer对应的地址。
第三个参数:对应是我们要读取或者写入的数据按字节计算的长度。
第四个参数:一般都是0;
第五个参数:用来保存客户端的地址信息。
第六个参数:就是前面这个地址的长度。
返回值:成功的话,返回值的大小代表实际读取的字节数。这个返回值,和count不一定相等。
如果出错的话,返回-1;
(5)、可以进行数据的收发。
sendto
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
第一个参数:是前面socket函数返回的fd。代表我们要和对应的客户端进行通讯。
第二个参数:是我们要发送或者接收的buffer对应的地址。
第三个参数:对应是我们要读取或者写入的数据按字节计算的长度。
第四个参数:一般都是0;
第五个参数:这个是我们要将数据发送到哪里?这个就是接收方的地址。这个参数一定要指定。
第六个参数:就是前面这个地址的长度。
返回值:成功的话,返回值的大小代表实际发送的字节数。这个返回值,和count不一定相等。
如果出错的话,返回-1;
(6)、通话结束,关闭连接。 close(fd);
UDP服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/un.h>
#include <sys/types.h>
#include <sys/socket.h>
int main()
{
//第一步,创建一个本地域的fd
int serverfd = socket( AF_UNIX, SOCK_DGRAM, 0 );
if(serverfd < 0)
{
perror("create socket fail");
return -1;
}
//第二步,设置服务器自己的地址
struct sockaddr_un serveraddr,clientaddr;
memset(&serveraddr,0,sizeof(serveraddr));
memset(&clientaddr,0,sizeof(clientaddr));
serveraddr.sun_family = AF_UNIX;
strcpy(serveraddr.sun_path,"/tmp/unixserver");
unlink("/tmp/unixserver");
//第三步,绑定服务器自己的地址
int ret = bind(serverfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if(ret < 0)
{
perror("server bind fail");
close(serverfd);
return -1;
}
char buf[128];
int size = sizeof(clientaddr);
while(1)
{
//第四步,等待客户端发送消息过来
bzero(buf,128);
ret = recvfrom(serverfd, buf, 128, 0 ,(struct sockaddr *)&clientaddr, &size );
printf("Recv from client ret=%d buf=%s clientpath:%s\n",ret,buf,clientaddr.sun_path);//clientaddr结构体里面保存了客户端的地址;
//第五步,将收到消息再发回去给客户端
ret = sendto(serverfd,buf,128,0,(struct sockaddr *)&clientaddr, size);
printf("Send to client ret=%d buf=%s\n",ret,buf);
}
//第六步,通讯结束,关闭连接。
close(serverfd);
}
(B)、客户端:
(1)、创建socket
(2)、配置服务器的地址。
(3)、还需要配置并且绑定自己的地址!!!!! 如果没有绑定自己地址的话,服务器就发不了数据给客户端
(4)、往这个地址发数据。
(5)、接收服务器发过来的数据。
(6)、通讯结束,关闭连接
Udp客户端unix
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/un.h>
#include <sys/types.h>
#include <sys/socket.h>
int main()
{
//第一步,创建一个UNIX域的socket
int ret;
int clientfd = socket(AF_UNIX, SOCK_DGRAM, 0 );
if(clientfd < 0)
{
perror("client socket create fail");
return -1;
}
//第二步,设置好服务器的地址,填好收件人的地址
struct sockaddr_un serveraddr,clientaddr;
memset(&serveraddr,0,sizeof(serveraddr));
memset(&clientaddr,0,sizeof(clientaddr));
serveraddr.sun_family = AF_UNIX;
strcpy(serveraddr.sun_path,"/tmp/unixserver");
//还要填好发件人的地址,要不然服务器不知道客户端的地址是多少
clientaddr.sun_family = AF_UNIX;
strcpy(clientaddr.sun_path,"/tmp/unixclient");//注意地址是不一样的
unlink("/tmp/unixclient");
//第三步,要绑定自己的地址
ret = bind(clientfd,(struct sockaddr *)&clientaddr,sizeof(clientaddr));
if(ret < 0)
{
perror("client bind error");
}
char buf[128];
int size = sizeof(serveraddr);
while(1)
{
//第四步,设置好地址之后,就可以通讯了
bzero(buf,128);
fgets(buf,128,stdin);
ret = sendto(clientfd,buf,128,0,(struct sockaddr *)&serveraddr,size);
bzero(buf,128);
recvfrom(clientfd,buf,128,0,(struct sockaddr *)&serveraddr,&size);
printf("Recv from server :%s\n",buf);
}
//第五步,通讯结束关闭
close(clientfd);
}
项目:实现一个FTP ( File Transmission Protocol )服务器。
1、启动服务器
2、启动客户端
3、客户端可以查看服务器指定的ftp目录内有什么文件。
4、客户端可以下载这个目录里面的文件到本地。
启动服务器
/home/gec/ftp/ ---> 1.c 2.c 3.c a b helloworld
$ ./ftp_client --------->先建立与服务器TCP的连接
| FTP> ls --------->发送一个命令给服务器
| 1.c 2.c 3.c a b helloworld --------->服务器解析这个命令,ls就是要查看ftp目录内包含有什么文件,然后通过opendir,把结果保存到一个字符数组,然后再发回来给客户端
| FTP> get helloworld --------->这里又发送一个get命令给服务器,把文件名提取出来,然后再和ftp路径名合成一个绝对路径,有这个路径之后,我们就可以打开这个文件,然后读取,并且发送给客户端。
| #################################
| FTP> exit
$ ls
$ helloworld
$chmod +x helloworld
$./helloworld
$ hello world !
服务器和客户端之间要实现通讯,先规定好一个协议。