Socket编程步骤:
服务器端编程的步骤:
- 加载套接字库,创建套接字(WSAStartup()/socket());
- 绑定套接字到一个IP地址和一个端口上(bind());
- 将套接字设置为监听模式等待连接请求(listen());
- 请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());
- 用返回的套接字和客户端进行通信(send()/recv());
- 返回,等待另一连接请求;
- 关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
客户端编程的步骤:
- 加载套接字库,创建套接字(WSAStartup()/socket());
- 向服务器发出连接请求(connect());
- 和服务器端进行通信(send()/recv());
- 关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
Socket的API:
1.socket:创建套接字
int socket(int domain, int type, int protocol);
- domain:指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族):
- AF_INET IPv4因特网域
- AF_INET6 IPv6因特网域
- AF_UNIX Unix域
- AF_ROUTE 路由套接字
- AF_KEY 密钥套接字
- AF_UNSPEC 未指定
- type:指明Socket的使用类型:
- SOCK_STREAM:使用套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证数据传输的正确性和高效性
- SOCK_DGRAM:数据报套接字定义了一种无连接的服,数据通过相互连接的报文进行传输,是无序的,并且不保证是可靠,无差错的。它使用数据报协议UDP
- SOCK_RAW:允许程序使用低层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用不便,主要用于一些协议的开发
- protocol:通常赋值为0(表示默认)
- IPPROTE_TCP TCP传输协议
- IPPROTE_UDP UDP传输协议
- IPPROTE_SCTP SCTP传输协议
- IPPROTE_TIPC TIPC传输协议
2.bind:IP端口号与相应描述字赋值:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
- sockfd 表示socket函数创建的通信文件描述符
- addr 一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址
- addrlen 表示所指定的结构体变量的大小
协议地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:
struct sockaddr{
unsigned short as_family; //协议族
char sa_data[4] //IP+端口号
}
等价于:
struct sockaddr_in {
sa_family_t sin_family; //协议族
in_port_t sin_port; //端口号
struct in_addr sin_addr; //IP地址结构体
unsigned char sin_zero[8] //没有实际意义
};
3.地址转换API:
int inet_aton(const char* straddr,struct in_addr* addrp);
//把字符串形为“192.168.1.123”转为网络能识别的形式
char* inet_ntoa(struct in_addr inaddr);
//把网络格式的ip地址转为字符串的形式
4.listen函数:监听设置
int listen(int sockfd, int backlog);
- sockfd 是一个引用SOCK_STREAM或者是SOCK_SEQPACKET类型的描述符
- backlog 指定了在请求队列允许的最大请求数
5.accpet函数:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//返回连接connect_fd
- addr: 这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。
- addrlen: 它也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。
accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。
6.send/recv函数:数据收发:
ssize_t send(int s, const char* buf, int len, int flags);
//buf是待发送信息内容
//flags一般设置为0
ssize_t recv(int s, char FAR *buf, int len, int flags);
//flags一般设置为0
7.connect函数:用户机连接主机:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
第一个参数即为客户端的socket描述字
第二参数为服务器的socket IP地址和端口号结构体指针
第三个参数为socket地址的长度
客户端通过调用connect函数来建立与TCP服务器的连接
demo1.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
int main(){
int s_fd;
struct sockaddr_in sock_addr;
//1.socket()
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("why socket failure");
exit(-1);
}
//2.bind()
sock_addr.sin_family = AF_INET;
sock_addr.sin_port = htons(8888);
inet_aton("192.168.95.130",&sock_addr.sin_addr);
int ret = bind(s_fd,(struct sockaddr*)&sock_addr,sizeof(struct sockaddr_in));
//这里因为第二个参数使用的高级参数,但是和原API的类型不同,所以要强转一下类型
//3.listen()
listen(s_fd,10);
//4.accept()
int c_fd = accept(s_fd,NULL,NULL);
//因为对客户端和服务器的地址不关心,所以设置为NULL
printf("Connected!\n");
while(1);
return 0;
}
运行结果:
服务器 demo
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
int main(){
int s_fd,n_read;
char buf[1024];
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(buf,0,sizeof(char));
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
//2.bind
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8888);
inet_aton("192.168.95.130",&s_addr.sin_addr);
int ret = bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
if(ret == -1){
perror("bind");
exit(-2);
}
//3.listen
listen(s_fd,10);
//4.accept
int c_len = sizeof(struct sockaddr_in);
int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&c_len);
if(c_fd == -1){
perror("accept");
exit(-4);
}else{
printf("%s connected!\n",inet_ntoa(c_addr.sin_addr));
}
//5.read
n_read = read(c_fd,buf,1024);
if(n_read == -1){
perror("write");
exit(-5);
}else{
printf("Content:%s\n",buf);
}
//6.write
write(c_fd,(void *)buf,strlen(buf));
return 0;
}
运行结果:
(暂时不清楚为什么在windows下只输入一个字符便与主机断开连接)
客户端 demo
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
int main(){
int c_fd;
struct sockaddr_in c_addr;
char buf[1024];
memset(buf,0,sizeof(char));
memset(&c_addr,0,sizeof(struct sockaddr));
//1.socket()
c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
perror("socket");
exit(-1);
}
//2.connect()
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(8888);
inet_aton("192.168.95.130",&c_addr.sin_addr);
int ret = connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in));
if(ret == -1){
perror("connect");
exit(-2);
}
//3.write()
int Wtypes = write(c_fd,(void *)buf,1024);
printf("Message has been sent!\n");
//4.read()
int n_read = read(c_fd,(void *)buf,strlen(buf));
if(n_read == -1){
perror("read");
exit(-4);
}else{
printf("Receive new message!\n");
}
return 0;
}
运行结果:
命名好像反了。。。问题不大