锐英源软件
第一信赖

精通

英语

开源

擅长

开发

培训

胸怀四海 

第一信赖

当前位置:锐英源 / 在线教育 / SOCKET网络通信开发公开课 / Sockets-Clients Socket客户端
服务方向
人工智能数据处理
人工智能培训
kaldi数据准备
小语种语音识别
语音识别标注
语音识别系统
语音识别转文字
kaldi开发技术服务
软件开发
运动控制卡上位机
机械加工软件
软件开发培训
Java 安卓移动开发
VC++
C#软件
汇编和破解
驱动开发
联系方式
固话:0371-63888850
手机:138-0381-0136
Q Q:396806883
微信:ryysoft

3.18 Sockets-Clients Socket客户端


要点:

  • 基本的客户端流程
  • socket()
  • connect()
  • close()和shutdown()
  • Unix客户端
  • 互联网客户端

3.18.1 基本的客户端流程

  • 第一要创建个socket

sd=socket(domain,type,protocol);
指定参数为:通信域,socket类型,通信协议。

  • 接着连接

connect(sd,&serv_addr,addrlen);
指定要通信的地址

  • 下面就可以发送或接收信息

read(sd,buf,nbytes);

write(sd,buf,nbytes);

是使用UNIX的I/O方式还是用发送接收函数,随后再讨论。

  • 最终,结束使用时,要释放socket

close(sd);

shutdown(sd,how);

  • 注意:对于无连接的协议,比如UDP,connect步骤不需要;发送用sendto(),接收用recvfrom()。

3.18.2 socket()函数

  • 调用socket()是所有网络活动的开始:

#include <sys/types.h>

#include <sys/socket.h>

int socket(int domain,int type,int protocol);
在成功时,返回一个非负数的整数,这个数理解为socket描述符,这类描述符和文件描述符类似。

  • 然而,文件描述符使用简单,open()函数调用后,就可以进行I/O操作了,但是sockt描述符不一样,它还需要其它操作。在典型的客户端里,下面要调用connect()函数。在典型的服务器端里,需要调用bind(),listen(),accept()函数,且另外一个描述符也要首先生成。
  • socket()函数采用三个整数输入参数。第一个是domain,它指定了协议簇(或者说通信域),它描述了数据怎样在网络之上发送出去。可用的值 在/usr/include/bits/socket.h里有:

域名

含意

PF_UNSPEC

0

未定义

PF_LOCAL

1

对应于PF_UNIX的POSIX下名称

PF_UNIX,PF_FILE

PF_LOCAL

Unix域socket

PF_INET

2

IPV4

PF_AX25

3

 

 

 

 

  • 需要关注的有:PF_LOCAL(PF_UNIX),PF_INET和PF_INET6。前2者适用于局限于本机的unix域socket,后2者适合于IPV4和IPV6。
  • 注意这些域可以用AF前缀来代替PF_,AF代表地址簇,PF代表协议簇。根据历史传承,打开的协议簇里可能会有多个地址簇。然而,这永远也不会发生,所以2者你都能用,但是严格来说,socket()函数里要用PF_前缀。
  • 第二个参数是type,有下列值:

SOCK_STREAM

SOCK_DGRAM

SOCK_RAW

SOCK_RDM

SOCK_SEQPACKET

SOCK_PACKET

  • 有些domain和type的组合是无效的。
  • 常用的是SOCK_STREAM,对应TCP,SOCK_DGRAM,对应UDP,这2者可以和PF_INET(6)结合使用。
  • 第三个参数protocal指定了socket要用哪个协议。通常,每类协议簇只提供一种协议,这个参数传入0表示用默认的协议。Raw socket有另外可能选项。
  • socket()函数的常见错误有:

EPROTONOSUPPOT:类型或协议不支持

EAFNOSUPPORT:指定的地址簇不支持

ENFILE:没有足够的内核内在来创建socket结构

EMFILE:进程文件表溢出

EACCES:权限否决

ENOBUFS,ENOMEM:没有足够的内在来创建socket

EINVAL:不可知的协议或协议簇

  • 记住,访问指定错误的代码如下:
#include  <errno.h>
…
sd=socket(PF_INET,SOCK_STREAM,0);
if(sd<0) {
perror(“Failed to create the socket”);
exit(errno);
}

3.18.3 connect()函数

  • 使用socket()函数得到socket描述符后,客户端的程序要用connect()函数来尝试和“插入”一些东西到socket里。

#include <sys/types.h>

#include <sys/socket.h>

int connect(int sd,const struct sockaddr*serv_addr,socklen_t addrlen);

  • 在成功时,connect()函数返回0,在失败时返回-1,errno里有错误编码。
  • 注意所有的参数都是输入类型的,connect()函数不会修改它们。
  • 第一个参数是socket()返回的描述符,第二个和第三个参数是要连接上的地址和长度。
  • 注意,有时第三个参数用int类型对象来代替socklen_t。根据历史上的安排,这些类型是一样的,要使用合适的类型。
  • 地址和怎样使用地址依赖于协议的类型。一般不会用struct sockaddr;这是个用来转换出来的类型,经典的代码如下:
struct  sockaddr_un addr;
…
sd=socket(PF_LOCAL,SOCK_STREAM,0);
…
if(connect(sd,(struct  sockaddr*)&addr,sizeof(addr)))
{
perror(“client connect”);exit(EXIT_FAILURE);
}
每类协议有它自己的地址结构。
  • 在上面情况下,SOCK_STREAM的使用指示了代码要尝试做一个基于连接的和指定地址(端口)关联的链路,地址由输入的第二个参数指定。这和SOCK_SEQPACKET类型一样。基于连接的协议只使用一次connect()函数。
  • 如果用SOCK_DGRAM,这会建立一个无连接的socket,这也会关联一个地址;这个地址规定了数据报从哪里发,哪里收。
  • 无连接的协议能够再次调用connect()函数来建立一个新地址关联。再次连接会释放所有已经建立的关联,这时候对于地址里的sa_family要设置为AF_UNSPEC。
  • connect()函数遇到的错误有:

EBADF:无效的描述符值

  • 在连接建立后,下面函数

int getsockname(int sd,const struct sockaddr*local_addr,socklen_t addrlen);

会得到和描述符对应连接的地址和端口的信息

3.18.4 close()和shutdown()函数

  • 结束一个socket最简单的方法是调用close()函数:

#include <unistd.h>
int close(int sd);

  • 它的参数是socket描述符,而不是通常的文件描述符,但是用法是和对文件形式一样的。
  • close()过后,再尝试对socket进行读写会导致错误。然而,只用close()有可能会导致些错误。
  • 第一个局限是close()的工作方式,它只是减少引用计数,并不执行实际的关闭操作,直到计数变为0.在fork函数执行前的一个socket描述符,意味着父子进程都要对它调用close()函数才会释放。
  • 在进程终止时,退出处理总是关闭所有描述符,这种情况下这个局限性不明显。在释放时有延迟有时候需要,但是你close()函数导致的延迟你不能控制。
  • 第二个局限来自连接的全双工特性。双方在调用close()函数后,都会终止,但是某一方想结束发送,但是保留接收未决数据的意愿。
  • 更详细的控制可以用shutdown()函数。

#include <sys/socket.h>

int shudown(int sd,int how);

这个函数在成功时返回0,失败的错误情况如下:

EBADF:描述符无效。

  • how参数可用下列值:

SHUT_RD:不再接收数据。在当前接收缓冲里的数据会被丢弃。

SHUT_WR:不再发送数据。在当前发送缓冲里的数据会被丢弃。

SHUT_RDWR:发收都停止。

  • 注意,对socket也可以设置SO_LINGER选项。这会让close()函数有更多控制,能让系统在实际释放socket前刷新socket的接收和发送缓冲。在这种情况下,对于close()的返回值要保持警惕,因为判断成功失败非常重要。

3.18.5 Unix方式Client

  • 下面有个例子,它从标准输入里读取字符,发送到本地的socket里,./mysock,接着结束。

#include <stdlib.h>

#include <stdio.h>

#include <unistd.h>

#include <sys/socket.h>

#include <sys/un.h>

#define MSG_LEN 1024

int main(void)
{
struct sockaddr_un uaddr;
int msg_len,sd;
char message[MSG_LEN];
              
uaddr.sun_family=AF_UNIX;
strcpy(uaddr.sun_path,”/tmp/mysock”);
sd=socket(PF_UNIX,SOCK_STREAM,0);
connect(sd,(struct  sockaddr*)&uaddr,sizeof(uaddr));
msg_len=strlen(fgets(message,MSG_LEN,stdin));
write(sd,message,msg_len);
close(sd);
 exit(EXIT_SUCCESS);
}

3.18.6 互联网客户端

  • 下面有一个使用PF_INET的socket例子,相当于echo服务器的客户端,echo使用端口7.
  • echo服务收到什么,发回什么
  • 程序需要的参数有:服务器IP地址(域名或点分隔形式),进入程序后连接服务器。随后从终端上读取输入,并发向服务器。下面读取服务器的回传结果,并打印。

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <unistd.h>

#include <string.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netdb.h>

#define MSG_LEN 1024

#define PORT_NUMBER 7

int main (int  argc, char **argv)
{
int sd, msg_len;
char message[MSG_LEN];
struct sockaddr_in addr;
struct hostent *hostent;
sd = socket (AF_INET, SOCK_STREAM, 0);
hostent = gethostbyname (argv[1]);addr.sin_family = AF_INET;
addr.sin_port = htons (PORT_NUMBER); //注意这里的网络顺序转换
memcpy (&addr.sin_addr,  hostent->h_addr, hostent->h_length);
connect (sd, (struct sockaddr *)&addr,  sizeof (addr));
msg_len = strlen (fgets (message, MSG_LEN,  stdin));
write (sd, message, msg_len);
memset (message, 0, MSG_LEN);
msg_len = read (sd, message, MSG_LEN);
write (STDOUT_FILENO, message, msg_len);
close (sd);
exit (0);
} 
友情链接
版权所有 Copyright(c)2004-2024 锐英源软件
统一社会信用代码:91410105098562502G 豫ICP备08007559号 最佳分辨率 1440*900
地址:郑州市金水区文化路97号郑州大学北区院内南门附近