socket编程

39 篇文章 0 订阅
30 篇文章 0 订阅
   对TCP/IPUDPSocket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。那么我想问:

1.         什么是TCP/IPUDP
2.         Socket在哪里呢?
3.         Socket是什么呢?
4.         你会使用它们吗?

什么是TCP/IPUDP

         TCP/IPTransmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
         UDPUser Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。
        这里有一张图,表明了这些协议的关系。

                                                                                

                                                                        图1

       TCP/IP协议族包括运输层、网络层、链路层。现在你知道TCP/IPUDP的关系了吧。
Socket在哪里呢?
       在图1中,我们没有看到Socket的影子,那么它到底在哪里呢?还是用图来说话,一目了然。



2

       原来Socket在这里。
Socket是什么呢?
       Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
你会使用它们吗?
       前人已经给我们做了好多的事了,网络间的通信也就简单了许多,但毕竟还是有挺多工作要做的。以前听到Socket编程,觉得它是比较高深的编程知识,但是只要弄清Socket编程的工作原理,神秘的面纱也就揭开了。
       一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。    生活中的场景就解释了这工作原理,也许TCP/IP协议族就是诞生于生活中,这也不一定。

      

3

       先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。


一、基本socket函数
Linux系统是通过提供套接字(socket)来进行网络编程的。网络的socket数据传输是一种特殊的I/O,socket也是一种文件描述符。socket也有一个类似于打
开文件的函数:socket(),调用socket(),该函数返回一个整型的socket的描述符,随后的连接建立、数据传输等操作也都是通过该socket实现。
1、socket函数
syntax:
   int socket(int domain, int type, int protocol);
功能说明:
   调用成功,返回socket文件描述符;失败,返回-1,并设置errno
参数说明:
  domain指明所使用的协议族,通常为PF_INET,表示TCP/IP协议;
  type参数指定socket的类型,基本上有三种:数据流套接字、数据报套接字、原始套接字
  protocol通常赋值"0"。
  两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。socket数据结构中包含这五种信息。
2、bind函数
syntax:  
   int bind(int sock_fd,struct sockaddr_in *my_addr, int addrlen);
功能说明:
   将套接字和指定的端口相连。成功返回0,否则,返回-1,并置errno.
参数说明:
    sock_fd是调用socket函数返回值,
  my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;
  struct sockaddr_in结构类型是用来保存socket信息的:
  struct sockaddr_in {
  short int sin_family;
  unsigned short int sin_port;
  struct in_addr sin_addr;
  unsigned char sin_zero[8];
  };
    addrlen为sockaddr的长度。
3、connect函数
syntax:  
    int connect(int sock_fd, struct sockaddr *serv_addr,int addrlen);
功能说明:
   客户端发送服务请求。成功返回0,否则返回-1,并置errno。
参数说明:
   sock_fd 是socket函数返回的socket描述符;serv_addr是包含远端主机IP地址和端口号的指针;addrlen是结构sockaddr_in的长度。
4、listen函数
syntax:
   int listen(int sock_fd, int backlog);
功能说明:
   等待指定的端口的出现客户端连接。调用成功返回0,否则,返回-1,并置errno.
参数说明:
   sock_fd 是socket()函数返回值;
   backlog指定在请求队列中允许的最大请求数
5、accecpt函数
syntax:  
   int accept(int sock_fd, struct sockadd_in* addr, int addrlen);
功能说明:
   用于接受客户端的服务请求,成功返回新的套接字描述符,失败返回-1,并置errno。
参数说明:
   sock_fd是被监听的socket描述符,
   addr通常是一个指向sockaddr_in变量的指针,
   addrlen是结构sockaddr_in的长度。
6、write函数
syntax:
    ssize_t write(int fd,const void *buf,size_t nbytes)
功能说明:
    write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数.失败时返回-1. 并设置errno变量.
    在网络程序中,当我们向套接字文件描述符写时有俩种可能:
      1)write的返回值大于0,表示写了部分或者是全部的数据.
      2)返回的值小于0,此时出现了错误.需要根据错误类型来处理.
        如果错误为EINTR表示在写的时候出现了中断错误.
        如果错误为EPIPE表示网络连接出现了问题.
7、read函数
syntax:
    ssize_t read(int fd,void *buf,size_t nbyte)
函数说明:
    read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0 表示已经读到文件的结束了,小于0表示出现了错误.
    如果错误为EINTR说明读是由中断引起的,
    如果错误是ECONNREST表示网络连接出了问题.
8、close函数
syntax:
int close(sock_fd);
说明:
当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:
函数运行成功返回0,否则返回-1

二、socket编程的其他函数说明
1、 网络字节顺序及其转换函数
1) 网络字节顺序
每一台机器内部对变量的字节存储顺序不同,而网络传输的数据是一定要统一顺序的。所以对内部字节表示顺序与网络字节顺序不同的机器,
一定要对数据进行转换,从程序的可移植性要求来讲,就算本机的内部字节表示顺序与网络字节顺序相同也应该在传输数据以前先调用数据转换函数,
以便程序移植到其它机器上后能正确执行。真正转换还是不转换是由系统函数自己来决定的。
2) 有关的转换函数
* unsigned short int htons(unsigned short int hostshort):
主机字节顺序转换成网络字节顺序,对无符号短型进行操作4bytes
* unsigned long int htonl(unsigned long int hostlong):
主机字节顺序转换成网络字节顺序,对无符号长型进行操作8bytes
* unsigned short int ntohs(unsigned short int netshort):
网络字节顺序转换成主机字节顺序,对无符号短型进行操作4bytes
* unsigned long int ntohl(unsigned long int netlong):
网络字节顺序转换成主机字节顺序,对无符号长型进行操作8bytes
注:以上函数原型定义在netinet/in.h里
2、IP地址转换
有三个函数将数字点形式表示的字符串IP地址与32位网络字节顺序的二进制形式的IP地址进行转换
(1) unsigned long int inet_addr(const char * cp):该函数把一个用数字和点表示的IP地址的字符串转换成一个无符号长整型,如:struct sockaddr_in ina
ina.sin_addr.s_addr=inet_addr("202.206.17.101")
该函数成功时:返回转换结果;失败时返回常量INADDR_NONE,该常量=-1,二进制的无符号整数-1相当于255.255.255.255,这是一个广播地址,所以在程序中调用iner_addr()时,一定要人为地对调用失败进行处理。由于该函数不能处理广播地址,所以在程序中应该使用函数inet_aton()。
(2)int inet_aton(const char * cp,struct in_addr * inp):此函数将字符串形式的IP地址转换成二进制形式的IP地址;成功时返回1,否则返回0,转换后的IP地址存储在参数inp中。
(3) char * inet_ntoa(struct in-addr in):将32位二进制形式的IP地址转换为数字点形式的IP地址,结果在函数返回值中返回,返回的是一个指向字符串的指针。
3、字节处理函数
Socket地址是多字节数据,不是以空字符结尾的,这和C语言中的字符串是不同的。Linux提供了两组函数来处理多字节数据,一组以b(byte)开头,是和BSD系统兼容的函数,另一组以mem(内存)开头,是ANSI C提供的函数。
以b开头的函数有:
(1) void bzero(void * s,int n):将参数s指定的内存的前n个字节设置为0,通常它用来将套接字地址清0。
(2) void bcopy(const void * src,void * dest,int n):从参数src指定的内存区域拷贝指定数目的字节内容到参数dest指定的内存区域。
(3) int bcmp(const void * s1,const void * s2,int n):比较参数s1指定的内存区域和参数s2指定的内存区域的前n个字节内容,如果相同则返回0,否则返回非0。
注:以上函数的原型定义在strings.h中。
以mem开头的函数有:
(1) void * memset(void * s,int c,size_t n):将参数s指定的内存区域的前n个字节设置为参数c的内容。
(2) void * memcpy(void * dest,const void * src,size_t n):功能同bcopy(),区别:函数bcopy()能处理参数src和参数dest所指定的区域有重叠的情况,memcpy()则不能。
(4) int memcmp(const void * s1,const void * s2,size_t n):比较参数s1和参数s2指定区域的前n个字节内容,如果相同则返回0,否则返回非0。
注:以上函数的原型定义在string.h中。

4、socket中TCP的三次握手建立连接详解

我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:

  • 客户端向服务器发送一个SYN J
  • 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
  • 客户端再想服务器发一个确认ACK K+1

只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:

image

图1、socket中发送的TCP三次握手

从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。

5、socket中TCP的四次握手释放连接详解

上面介绍了socket中TCP的三次握手建立过程,及其涉及的socket函数。现在我们介绍socket中的四次握手释放连接的过程,请看下图:

image

图2、socket中发送的TCP四次握手

图示过程如下:

  • 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
  • 另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
  • 一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
  • 接收到这个FIN的源发送端TCP对它进行确认。

这样每个方向上都有一个FIN和ACK。


例子:

客户端程序:

#include<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<errno.h>
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>

#define MAXLINE 4096

int main(int argc, char** argv)
{
int sockfd, n;
char recvline[4096], sendline[4096];
struct sockaddr_in servaddr;

if( argc != 2){
printf(
"usage: ./client <ipaddress>\n");
exit(
0);
}

if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf(
"create socket error: %s(errno: %d)\n", strerror(errno),errno);
exit(
0);
}

memset(
&servaddr, 0, sizeof(servaddr));
servaddr.sin_family
= AF_INET;
servaddr.sin_port
= htons(6666);
if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
printf(
"inet_pton error for %s\n",argv[1]);
exit(
0);
}

if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
printf(
"connect error: %s(errno: %d)\n",strerror(errno),errno);
exit(
0);
}

printf(
"send msg to server: \n");
fgets(sendline,
4096, stdin);
if( send(sockfd, sendline, strlen(sendline), 0) < 0)
{
printf(
"send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(
0);
}

close(sockfd);
exit(
0);
}

服务器端程序:

#include<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<errno.h>
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>

#define MAXLINE 4096

int main(int argc, char** argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[4096];
int n;

if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
printf(
"create socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(
0);
}

memset(
&servaddr, 0, sizeof(servaddr));
servaddr.sin_family
= AF_INET;
servaddr.sin_addr.s_addr
= htonl(INADDR_ANY);
servaddr.sin_port
= htons(6666);

if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
printf(
"bind socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(
0);
}

if( listen(listenfd, 10) == -1){
printf(
"listen socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(
0);
}

printf(
"======waiting for client's request======\n");
while(1){
if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
printf(
"accept socket error: %s(errno: %d)",strerror(errno),errno);
continue;
}
n
= recv(connfd, buff, MAXLINE, 0);
buff[n]
= '\0';
printf(
"recv msg from client: %s\n", buff);
close(connfd);
}

close(listenfd);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值