对socket通信的理解

       socket是“插座”的意思,两个进程之间通过socket来进行通信可以用手机来比喻,一般都是客户端向服务器发出访问请求,则客户端类比为拨电话的人,而服务器类比为接电话的人。两个用户要对话首先双方都要有一部手机,相当于双方用socket()函数创建一个socket套接字一样,然后这部手机要有一个电话号它才能有利用价值,也就是将套接字与通信地址进行挂钩。对于客户端,也就是准备拨电话的人,他必须要知道他想给谁打也就是对方的电话号是多少,通过connect()函数将它想访问的通信地址与该套接字进行连接。而接电话的人他要给这部电话一个电话号,其他人才能打过来,所以利用bind()给这部电话绑定一个电话号。这样就能看出,如果双方想要成功的建立连接,双方的通信地址必须是一样的,就像我要给你打电话,必须拨打的是你的手机号一样。

那这个手机就有不同了,有的是国际号,有的是家里的小号,不同的号对电话的要求不太一样,拿着小灵通就打不到国外了……而再然后,有的打电话就必须对方接到再进行通话,有的可以留言,对方什么时候有空再听。前者代表的是协议族,有本地通信和ipv4 ipv6 网络通信,后者表示连接类型,必须连接是TCP通信类型,可以留言的是UDP通信类型,通过选择参数选择合适的电话机。

对这个电话号码也是有要求的,家里的小号是短号,国外的可能就多好几位,对于本地通信和网络通信有通用的通信地址,不需要我们自己定义,分别是struct  sockaddr_un和struct sockaddr_in,但是在bind和connect过程中为了方便,我们都把他们强制转换为通用的一个地址格式struct sockaddr。有一点要注意就是在网络通信地址里的端口号,由于网络字节和主机字节很多时候是不一样的,网络字节是大端网络,而主机字节很多情况下是小端网络,也就是假设端口号是1234,那么在计算机里存储是地址由低到高:0x34 0x12,但在网络上存储时地址由低到高:0x12 0x34,这个时候利用htons()函数转变字节顺序。

一:本地通信和UDP通信

都不是面向连接的,过程大致为

服务器:
   
1)创建socket,使用socket函数
   
2)准备通信地址,使用结构体类型
   
3)绑定socket和通信地址,使用bind函数
   
4)进行通信,使用read/write函数
   
5)关闭socket,使用close函数
客户端:
   
1)创建socket,使用socket函数
   
2)准备通信地址,服务器的地址
   
3)链接socket和通信地址,使用connect函数
   
4)进行通信,使用read/write函数
   
5)关闭socket,使用close函数

二:TCP通信

是面向连接的,所以接电话的人先给自己手机绑定一个手机号以后,就一直等着电话响,这就是listen(),等到发现电话响了,马上把电话接起来,这就是accept(),然后双方就可以进行通话了。注意这里accept()函数会返回一个文件描述符,服务器是通过返回的这个描述符来通信而不是像UDP一样用socket的套接字来通信。

过程如下:

服务器:
1)创建socket,使用socket函数
2)准备通信地址,使用结构体类型
3)绑定socket和通信地址,使用bind函数
4)监听,使用listen函数
5)响应客户端的连接请求,使用accept函数
6)进行通信,使用read/write函数
7)关闭socket,使用close函数
客户端:
1)创建socket,使用socket函数
2)准备通信地址,使用结构体类型
3)连接socket和通信地址,使用connect函数
4)进行通信,使用read/write函数
5)关闭socket,使用close函数


学习中遇到的一些问题和解决方法:

 

一:bind出现Address already in use:

         1:本地通信:unixdomain socket 与网络socket编程最大的不同在于地址格式不同,用结构体socketaddr_un表示,网络地址是由ip加端口号决定,而domain socket的地址是一个socket类型的文件在文件系统的路径,该文件由bind()函数创建并绑定,如果bind时该文件已经存在,则绑定失败。因此每次把创建的socket文件删除或者bind一个新的socket文件。

         2:网络通信:程序第一次可以正确运行并且结束,但第二次开始就会出现错误bind:Address already in use,只能用ctrl+c强制结束,这个问题是由TCP 套接字状态TIME_WAIT 引起,在套接字通过close()正常关闭后,会保留2到4分钟,该套接字才会删除,同时与该套接字绑定的端口和本地地址才可以被重新绑定。所以如果我们运行过一次程序,用close(sockfd)删除这个套接字之后,其实要等到几分钟之后,才会真正删除,这段时间内的端口和本地地址是仍然与该套接字绑定的,如果你立即再执行一遍程序,便会提示:这个地址正在使用中。

         有一个绕过TIME_WAIT的方法就是,给套接字设置一下,让它可以绑定一个复用的端口就可以了。具体是利用setsockopt函数。

int setsockopt(SOCKET s,int level,intoptname,const char* optval,int optlen)

SOCKET(套接字): 指向一个打开的套接口描述字

level:(级别): 指定选项代码的类型。

SOL_SOCKET: 基本套接口

IPPROTO_IP: IPv4套接口

IPPROTO_IPV6: IPv6套接口

IPPROTO_TCP: TCP套接口

optname(选项名): 选项名称

optval(选项值): 是一个指向变量的指针 类型:整形,套接口结构, 其他结构类型:linger{},timeval{ }

optlen(选项长度) :optval 的大小

返回值:标志打开或关闭某个特征的二进制选项

 

因此可以加这么一句:

int reuse = 1;

setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,&reuse, sizeof(reuse));

(SO_REUSERADDR 允许重用本地地址和端口,充许绑定已被使用的地址(或端口号))

 

二:bind: Cannot assign requested address

         首先作为服务器bind时候,必须绑定自己的ip地址,不能乱写成别的ip地址,另外在tcp中端口号不能被占用,可以加上上一题里那两句允许复用的程序,就可以绑定占用的端口。程序测试发现udp里可以绑定正在使用中的端口(留疑问)。

         然后服务器和客户端要通信,两端通信地址必须完全一样,ip和端口都要一致。

 

 三:connect: Connection refused

客户端要和服务器连接时,服务器必须是已经打开的,所以要先开服务器再开客户端。

 

四:read:Bad address

大部分是由于读写内存地址错误的问题

五:read: Connection reset by peer

客户端要读数据时,此时服务器已经关闭了

六:服务器这端在read()数据时,会接收两次数据

如果write()端的发送数据大小大于read()端的接收数据大小,数据会分成两批进行接收。因此要保持两端的数据大小一致或者read端大于write端

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值