龙云尧个人博客,转载请注明出处。
CSDN地址:http://blog.csdn.net/Michael753951/article/details/72990141
个人blog地址:http://yaoyl.cn/socketbian-cheng-xiao-jie/
本部分内容是对之前博客【 Ubuntu下进行Socket编程】中的补充说明,在这篇博客中,我将详细解释Socket编程的代码,分析编程的流程和思想。
声明:本部分代码分别引用自【Linux C Socket编程原理及简单实例】以及【Linux网络编程:socket文件传输范例】。
首先我们先分析第一份较为简单的代码,使用Socket编程完成一个两个控制台窗口之间通信的demo。
在分析之前,我推荐你先看一下这篇博客【简单理解Socket】,上面很详细的解释说明了Socket编程的过程中,服务器端和客户端分别需要调用的函数和调用流程。比如博客中下面这张图,很重要也很直观的。
接下来我们开始分析代码,看代码的过程中,我们需要不断回头看看上面这张流程图,方便对整个代码进行理解剖析。
服务器端代码如下。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MYPORT 8887
#define QUEUE 20
#define BUFFER_SIZE 1024
int main()
{
///获得sockfd
// SOCK_STREAM 表明使用TCP协议(有序,流)
// 参考地址 http://baike.baidu.com/link?url=msvzh3jij6QHrqSMUVsst9P7o0wCNRJFS2qwZ_G5uu0hBL090wOB-4Nfgv7ye2-ZlQ3ZnnR4tqueYg6ThnqXSNP6aRe371EX2dhl5uYZ_jm
int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);
///定义sockaddr_in,用来表示接受方的IP地址
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET; // 设置地址家族
// htons是将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为:高位字节存放在内存的低地址处。 参考地址:http://baike.baidu.com/link?url=QUJ7h2uCP-Fag7xTlSgVCsZyGFr-hqJuaO-L5wRHbH7ODChk-vsHwsiipqVqIiJeNf1dCz4aeJ7SEvC-324Hfa
// 绑定端口号至8887号
server_sockaddr.sin_port = htons(MYPORT); // 设置端口
// htonl将主机数转换成无符号长整型的网络字节顺序。本函数将一个32位数从主机字节顺序转换成网络字节顺序。
// INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 一般来说,在各个系统中均定义成为0值。 参考地址:http://baike.baidu.com/link?url=UnkEXjpR6yhwsqWJ9d_77IzqsbEPaiO_e8WI355TORvFwpgf9zO4sQcBLilHuKxTYEOanDCWxGedtp_8-7BfVDZwyjN8ZDJDLrXitPwr49Bf5E7OwvEKHQj6EjZQHGcPU8DpDDsdeixUmYnZxgJMya
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置地址
///bind,成功返回0,出错返回-1
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
{
perror("bind");
exit(1);
}
///listen,成功返回0,出错返回-1
// 消息队列长度为20
if(listen(server_sockfd, QUEUE) == -1)
{
perror("listen");
exit(1);
}
///客户端套接字
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
///成功返回非负描述字,出错返回-1
int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);
if(conn<0)
{
perror("connect");
exit(1);
}
while(1)
{
memset(buffer,0,sizeof(buffer));
int len = recv(conn, buffer, sizeof(buffer),0);
if(strcmp(buffer,"exit\n")==0)
break;
fputs(buffer, stdout);
send(conn, buffer, len, 0);
}
close(conn);
close(server_sockfd);
return 0;
}
仔细查看代码,各种参数的设置原因我已经写在注释中了,就不再赘述。稍加注意我们便可以发现服务器端代码中,依次调用了socket, bind, listen, accept, recv, close。整个流程是符合我们之前放出来的那张图的,我们在计算机网络中也学习过,5层网络模型中,socket套接字担任了应用层和网络层之间通信的任务,我们也知道了每个socket套接字会绑定一个特定的端口,从而接受这个端口传入的讯息。
代码中,首先使用socket函数定义消息使用IPv4网络,建立一个面向连接的稳定数据传输,即TCP传输。
然后作者定义了一个sockaddr_in结构体,用来存放服务器的信息。sockaddr_in中存放了客户端的网络通信方式——IPv4,TCP,以及监听的ip地址。(这里监听的0.0.0.0,表示监听本机上所有的ip地址,这里的ip地址是指5层网络中的第4层网络层的ip地址,即服务器端自身的ip地址,这部分资料可以参考【关于socket绑定INADDR_ANY】)。上面有比较详细的解释。
接下来调用bind函数绑定socket,同时调用listen开始监听,使用accept接收消息队列中的消息内容,监听的ip和端口号已经在sockaddr_in中已经定义好了。
接着使用一个while循环忙等待,等待调用recv让服务端收到从其他客户端发来的消息。send表示将消息发送出去。
只要将服务端的代码看懂,类似的,我们也能够很简单的看懂客户端的代码内容。请自行对着那张流程图和代码进行比对阅读。
仔细查看demo2使用TCP传输文件的例子,其实和上一个demo没什么本质上的区别,流程的关键代码都一致。不过稍微注意的是,在这一份demo中多了一行代码。
setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
关于这一行代码的解释,我在这里就不多说了,因为我也并没有很看明白,如果你有兴趣的话,可以参考这篇博客,上面有很详尽的解释以及说明。【setsockopt()用法(参数详细说明)】
到这里其实想不出还有什么需要在解释的了,代码很简单,因为这两份demo都很纯粹,简单几行代码便能够展示清楚使用TCP变成的操作方法和顺序(如果你想使用UDP编程的话,方法类似)。