int socket(int domain, int type, int protocol);
// domain:采取的协议族,一般为 PF_INET;
//type:数据传输方式,一般为 SOCK_STREAM;
//protocol:使用的协议,一般设为 0 即可。
//成功时返回文件描述符,失败时返回 -1
创建套接字的函数 socket 的三个参数的含义:
domain:使用的协议族。一般只会用到 PF_INET,即 IPv4 协议族。
type:套接字类型,即套接字的数据传输方式。主要是两种:SOCK_STREAM(即 TCP)和 SOCK_DGRAM(即 UDP)。
protocol:选择的协议。一般情况前两个参数确定后,protocol 也就确定了,所以设为 0 即可。
协议就是为了完成数据交换而定好的约定
名称 | 协议族 |
---|---|
PF_INET | IPv4互联网协议族 |
PF_INET6 | IPv6互联网协议族 |
PF_LOCAL | 本地通信的UNIX协议族 |
PF_PACKET | 底层套接字的协议族 |
PF_IPX | IPX Novell协议族 |
SOCK_STREAM 代表的是 TCP 协议,会创建面向连接的套接字,有如下特点:
可靠传输,传输的数据不会消失。
按序传输。
传输的数据没有边界:从面向连接的字节流角度理解。接收方收到数据后放到接收缓存中,用户使用 read 函数像读取字节流一样从中读取数据,因此发送方 write 的次数和接收方 read 的次数可以不一样。
SOCK_DGRAM 代表的是 UDP 协议,会创建面向消息的套接字,有如下特点:
快速传输。
传输的数据可能丢失、损坏。
传输的数据有数据边界:这意味着接收数据的次数要和传输次数相同,一方调用了多少次 write(send),另一方就应该调用多少次 read(recv)。
限制每次传输的数据大小。
因为有这种情况:同一协议族中存在多个数据传输方式相同的协议,所以还需要第三个参数 protocol 来指定具体协议。
但是 PF_INET(IPv4 协议族)下的 SOCK_STREAM 传输方式只对应 IPPROTO_TCP 一种协议,SOCK_DGRAM 传输方式也只对应 IPPROTO_UDP 一种协议,所以参数 protocol 只要设为 0 即可。
int tcp_socket = socket(PF_INET, SOCK_STREAM, 0);
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); // 和上面效果一样
什么是协议?在收发数据中定义协议有何意义?
协议就是为了完成数据交换而定好的约定。因此,定义协议意味着对数据传输所必需的的承诺进行定义。
面向连接的 TCP 套接字传输特性有 3 点,请分别说明。
下面哪些是面向消息的套接字的特性?
下列数据适合用哪类套接字进行传输?
何种类型的套接字不存在数据边界?这类套接字接收数据时需要注意什么?
TCP套接字,即连接导向型套接字,不存在收发数据的边界。因此,I/O函数的调用次数不具有意义。重要的不是函数的调用次数,而是数据的收发量。因此,必须编写代码,以便发送的数据量和接收的数据量相匹配,特别是不能编写依赖于函数的调用次数的代码。
tcp_server. c和 tcp_client c中需多次调用read函数读取服务器端调用1次wrie函数传递的字符串。更改程序,使服务器端多次调用(次数自拟) write函数传输数据,客户端调用1次read函数进行读取。为达到这一目的,客户端需延迟调用read函数,因为客户端要等待服务器端传输所有数据。 Windows和 Linux都通过下列代码延迟read或recv函数的调用
for(i=0;i<3000;i++)
printf("Wait time ‰d \n",i);
让CPU执行多余任务以延迟代码运行的方式称为“ Busy Waiting"。使用得当即可推迟函数调用。
代码如下:
/*****************************tcp_serv.c*********************************/
#include
#include
#include
#include
#include
#include
#include
void error_handling(char *message);
int main(int argc, char *argv[])
{
int serv_sock;
int clnt_sock;
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size;
char message[]="Hello World!";
if(argc!=2){
printf("Usage : %s \n" , argv[0]);
exit(1);
}
serv_sock=socket(PF_INET, SOCK_STREAM, 0);
if(serv_sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1)
error_handling("bind() error");
if(listen(serv_sock, 5)==-1)
error_handling("listen() error");
clnt_addr_size=sizeof(clnt_addr);
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
if(clnt_sock==-1)
error_handling("accept() error");
write(clnt_sock, message, 4);//分多次发送数据
write(clnt_sock, message+4, 4);
write(clnt_sock, message+8, 4);
write(clnt_sock, message+12, sizeof(message)-12);
close(clnt_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
/*****************************tcp_clnt.c*********************************/
#include
#include
#include
#include
#include
#include
#include
void error_handling(char *message);
int main(int argc, char* argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len=0;
int idx=0, read_len=0, i;
if(argc!=3){
printf("Usage : %s \n" , argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_STREAM, 0);
if(sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
error_handling("connect() error!");
for(i=0; i<100; i++) // busy waiting!!
printf("Wait time %d \n", i);
read(sock, message, sizeof(message));//一次全部接收数据
printf("Message from server: %s \n", message);
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
#include
#include
#include
#include
#include
#include
#include
/*
domain
type
protocol
*/
void error_handling(char *message);
int main(int argc, char* argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len=0;
int idx=0, read_len=0;
if(argc!=3){
printf("Usage : %s \n" , argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_STREAM, 0);//TCP
if(sock == -1)
error_handling("socket() error");
//填写目的端的IP地址和端口
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
error_handling("connect() error!");
//如果服务端进程关闭了socket连接,那么客户端会接收到服务端发送过来的一个 TCP 协议的 FIN 数据包,然后客户端进程中原本阻塞着等待接收服务端进程数据的 read函数此时就会被唤醒,返回一个值 0。
//这跟我们前面提到两种文件读到文件末尾返回 EOF(值为-1)的情况有点差别,所以在程序中从 socket 进行读取操作时,判断数据流结束的标志不是 -1 而是 0。
while(read_len=read(sock, &message[idx++], 1))//每次只读一个字节的消息
{
if(read_len==-1)
error_handling("read() error!");
str_len+=read_len;
}
printf("Message from server: %s \n", message);
printf("Function read call count: %d \n", str_len);
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
/******************** input******************
description:
服务器端发送了13字节的数据,客户端调用13次read函数进行读取。
content:
./01-tcp_client 127.0.0.1 9190
*******************************************/
/******************** output******************
description:
content:
Message from server: Hello World!
Function read call count: 13
*******************************************/
#include
#include
#include
#include
#include
#include
#include
void error_handling(char *message);
int main(int argc, char *argv[])
{
int serv_sock;
int clnt_sock;
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size;
char message[]="Hello World!";
if(argc!=2){
printf("Usage : %s \n" , argv[0]);
exit(1);
}
serv_sock=socket(PF_INET, SOCK_STREAM, 0);
if(serv_sock == -1)
error_handling("socket() error");
//填写服务器端的IP地址和端口
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1)
error_handling("bind() error");
if(listen(serv_sock, 5)==-1)
error_handling("listen() error");
clnt_addr_size=sizeof(clnt_addr);
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
if(clnt_sock==-1)
error_handling("accept() error");
write(clnt_sock, message, sizeof(message));
close(clnt_sock);//这段代码会发送FIN包
close(serv_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
/******************** input******************
description:
content:
./02-tcp_server 9190
*******************************************/
/******************** output******************
description:
向客户端发送"Hello World!"这个消息
content:
*******************************************/