参考书籍:《TCP/IP》网络编程,作者【韩】尹圣雨
测试环境:Ubuntu 10.10
GCC版本:4.4.5
一、关于协议
协议就是为了完成数据交换而定好的约定。
二、深入理解创建套接字
前边已经介绍过创建套接字函数,后续主要解释该函数参数意义。
头文件:#include <sys/socket.h>
函数功能:创建套接字
返回值:成功时返回文件描述符,失败时返回-1
函数原型:int socket(int domain, int type, int protocol);
函数参数:
domain——套接字中使用的协议族(Protocol Family)信息。
type——套接字数据传输类型信息。
protocol——计算机间通信中使用的协议信息。
三、协议族(Protocol Family)
socket函数的第一个参数传递套接字中使用的协议分类信息。此协议信息为协议族,可分为如下几类:
名称 | 协议族 |
PF_INET | IPv4互联网协议族 |
PF_INET6 | IPv6互联网协议族 |
PF_LOCAL | 本地通信的UNIX协议族 |
PF_PACKET | 底层套接字的协议族 |
PF_IPX | IPX Novell协议族 |
四、套接字类型(Type)
socket函数的第一个参数PF_INET协议族中也存在多种数据传输方式。套接字的协议族确定后,通过第二个参数套接字类型确定数据具体传输方式。
五、套接字类型
这里主要介绍面向连接的套接字和面向消息的套接字这两种数据传输方式。
面向连接的套接字(SOCK_STREAM:流式套接字):
* 传输过程中数据不会消失
* 按序传输数据
* 传输的数据不存在数据边界(发送数据次数不等于接收数据次数)
概括:可靠的、按序传递的、基于字节的面向连接的数据传输方式的套接字
面向消息的套接字(SOCK_DGRAM:数据报套接字):
* 强调快速传输而非传输顺序
* 传输的数据可能丢失也可能损坏
* 传输的数据有数据边界
* 限制每次传输的数据大小
概括:不可靠的、不按序传递的、以数据的高速传输为目的的套接字
六、协议的最终选择
socket函数的第三个参数决定了最终采用的协议。
使用场景:同一协议族中存在多个数据传输方式相同的协议。
如下:
面向连接的数据传输方式——SOCK_STREAM:
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
面向消息的数据传输方式——SOCK_DGRAM:
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
七、面向连接的套接字:TCP套接字示例
服务器端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void errorHandling(const char* message);
int main(int argc, char* argv[])
{
int servSock = -1;
int clntSock = -1;
struct sockaddr_in servAddr;
struct sockaddr_in clntAddr;
socklen_t clntAddrSize;
char message[] = "Hello World!";
if(2 != argc)
{
printf("Usage:%s <port>\n", argv[0]);
exit(1);
}
servSock = socket(PF_INET, SOCK_STREAM, 0);
if(-1 == servSock)
errorHandling("socket() error");
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_famliy = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(atoi(argv[1]));
if(-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
errorHandling("bind() error");
if(-1 == listen(servSock, 5))
errorHandling("listen() error");
clntAddrSize = sizeof(clntAddr);
clntSock = accept(servSock, (struct sockaddr*)&clntAddr, &clntAddrSize);
if(-1 == clntSock)
errorHandling("accept() error");
write(clntSock, message, sizeof(message));
close(clntSock);
close(servSock);
return 0;
}
void errorHandling(const char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void errorHandling(const char* message);
int main(int argc, char* argv[])
{
int sock = -1;
struct sockaddr_in servAddr;
char message[30] = {0};
int strLen = -1;
int idx = 0;
int readLen = 0;
if(3 != argc)
{
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(1);
}
sock = socket(PF_INET, SOCK_STREAM, 0);
if(-1 == sock)
errorHandling("socket() error");
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = inet_addr(argv[1]);
servAddr.sin_port = htons(atoi(argv[2]));
if(-1 == connect(sock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
errorHandling("connect() error");
while(readLen = read(sock, &message[idx++], 1))
{
if(readLen == -1)
errorHandling("read() error");
strLen += readLen;
}
printf("Message from server: %s\n", message);
printf("Function read call count: %d\n", strLen);
close(sock);
return 0;
}
void errorHandling(const char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
运行结果:
分析:
服务端只发送一次数据,但是客户端可以多次调用read函数获取数据,证明了面向连接套接字传输的数据不存在数据边界特点!