一、学习内容:
1、理解socket创建套接字
2、有关协议族
3、区分TCP和UDP
- socket原型
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
/*
成功时返回文件描述符,失败时返回-1
domain: 套接字中使用的协议族(Protocol Family)
type: 套接字数据传输的类型信息
protocol: 计算机间通信中使用的协议信息
*/
1.1、协议族
名称 | 协议族 |
---|---|
PF_INET | IPV4 互联网协议族 |
PF_INET6 | IPV6 互联网协议族 |
PF_LOCAL | 本地通信 Unix 协议族 |
PF_PACKET | 底层套接字的协议族 |
PF_IPX | IPX Novel 协议族 |
socket 函数的第一个参数主要用的是PF_INET对应的IPV4协议族,其他协议并不常用,因此中重点放在PF_INET协议族上。另外,套接字中实际采用的最终协议是通过第三个参数确定的。
1.2、套接字类型
socket 函数的第二个参数指的是套接字的数据传输方式,只有这样才能决定创建的套接字的数据传输方式。已经通过第一个参数传递了协议族信息,为什么还要决定数据传输方式?问题就在于,决定了协议族并不能同时决定数据传输方式。换言.之, socket 函数的第一个参数 PF_INET 协议族中也存在多种数据传输方式。
1.2、协议的最终选择
socket 函数的第三个参数决定传输最终采用的协议。那么为什么前面已经通过socket函数的前两个参数确定了协议族和传输类型为什么还要第三个参数呢??, 因为有可能同一协议族下存在多个传输方式相同的协议。那么为什么前面socket函数的第三个参数用0呢?? 因为在同一协议下满足该传输方式的协议只有一种。
TCP:
int tcp_socket=socket(PF_INET,SOCK_SREAM,IPPROTO_TCP);
参数说明:
PF_INET指定ipv4协议族。
SOCK_SREAM指定传输方式为面向连接型(特点:
1:传输过程中数据不会消失
2:按顺序传输
3:传输数据不存在数据边界当套接字内部的缓冲区填满后将停止传输,等缓冲区空闲时继续传输)
IPPROTO_TCP指定最终传输协议为TCP等价与传入0
UDP:
int tcp_socket=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
参数说明:
PF_INET指定ipv4协议族。
SOCK_DGRAM指定传输方式为面向消息型(特点:
1:传输速度快不在乎传输顺序
2:传输数据可能丢失
3:传输数据存在数据边界
4:限制每次传输数据大小)
IPPROTO_UDP指定最终传输协议为UDP等价与传入0
- 示例
client:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
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 <IP> <port>\n", argv[0]);
exit(1);
}
//创建套接字,此时套接字并不马上分为服务端和客户端。如果紧接着调用 bind,listen 函数,将成为服务器套接字
//如果调用 connect 函数,将成为客户端套接字
//若前两个参数使用PF_INET 和 SOCK_STREAM,则可以省略第三个参数 IPPROTO_TCP
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]));
//调用 connect 函数向服务器发送连接请求
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
error_handling("connect() error!");
//当read函数返回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);
}
server:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
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 <port>\n", argv[0]);
exit(1);
}
//调用 socket 函数创建套接字
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]));
//调用 bind 函数分配ip地址和端口号
if (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error");
//调用 listen 函数将套接字转为可接受连接状态
if (listen(serv_sock, 5) == -1)
error_handling("listen() error");
clnt_addr_size = sizeof(clnt_addr);
//调用 accept 函数受理连接请求。如果在没有连接请求的情况下调用该函数,则不会返回,直到有连接请求为止
clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -1)
error_handling("accept() error");
//稍后要将介绍的 write 函数用于传输数据,若程序经过 accept 这一行执行到本行,则说明已经有了连接请求
write(clnt_sock, message, sizeof(message));
close(clnt_sock);
close(serv_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
注意!!!:
这里服务器一次性发了13个字节数,但客户端读取了13次数据每次读取一个字节,这就是TCP传输特性:可靠,有序。
————————————————
原文链接:https://blog.csdn.net/BBQ_hxl/article/details/109272878