基于tcp/ip_socket编程代码
linux 中 socket 实现TCP/IP的通信机制的原理:
首先确定一个网络进程需通过“IP地址+端口号”,也称为 socket。
内核中提供的API:
-
指定接受或发送的特定数据格式
int socket(int domain, int type, int protocol);
参数1domain:指定IP地址的格式
参数2type:指定传输层的协议
参数3protocol:一般为0,指定当前格式为默认格式
成功返回该socket对应的文件描述符,失败返回-1 -
绑定socket对应的网络进程的IP+端口号
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数1sockfd:对应的socket文件描述符
参数2addr:网络进程的服务器地址描述符
参数3addrlen:描述符大小 -
初始化网络进程的监听机制
int listen(int sockfd, int backlog);
参数1sockfd:对应的socket文件描述符
参数2backlog:排队建立3次握手队列和刚刚建立3次握手队列的最大链接数和,
可通过"cat /proc/sys/net/ipv4/tcp_max_syn_backlog"查看系统默认配置 -
阻塞等待客户端连接,进入监听模式
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数1sockfd:对应的socket文件描述符
参数2addr:用于接收客户端地址描述符
参数3addrlen:客户端地址的长度,防止溢出 -
客户端于服务器建立连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数1sockfd:对应的socket文件描述符
参数2addr:网络进程的服务器地址描述符
参数3addrlen:描述符大小
补充一个问题
为什么服务器需要调用Bind,而客户端不用呢?
答:服务器具有固定的端口号,IP地址可以设为本机下所支持的IP号即INADDR_ANY,客户端通过connect连接服务器时,服务器为客户端会自动分配一个未使用的端口号,并且客户端的IP也默认为客户端机上的本地IP,这里就要注意了,如果客户端调用bind绑定,则服务器则需要给客户端分配客户端绑定的端口号,但是该端口号又有可能在服务器端已被使用,导致连接失败。
所以,一般客户端不需要多此一举去bind,让服务器分配比较适合。
好了,下面开始看一段代码,了解简单实现的机制
实现功能:
客户端发送字符串数据给服务器,服务器将其转为大写,返回给客户端
可用命令netstat -apn|grep 8000查看连接情况
服务器代码:
//server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <stdlib.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
//服务器端口号
#define SERV_PORT 8000
//listen的最大队列数
#define MAX_LOG 2048
//读写缓冲区大小
#define MAX_LINE 128
void sys_err(char *ptr)
{
perror(ptr);
exit(1);
}
int main(void){
int ser_fd,cli_fd,len;
socklen_t cliaddr_len;
char buf[MAX_LINE];
char ipstr[128];
int i = 0;
struct sockaddr_in servaddr,cliaddr;
//1.socket
ser_fd = socket(AF_INET,SOCK_STREAM,0);
//2.bind
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(ser_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0){
sys_err("bind");
}
//3.listen
if(listen(ser_fd, MAX_LOG)<0)
sys_err("listen");
//4.while accept
while(1){
cliaddr_len = sizeof(cliaddr);
cli_fd = accept(ser_fd, (struct sockaddr *)&cliaddr,
&cliaddr_len);
//5.do
len = read(cli_fd, buf, MAX_LINE);
for(i = 0;i<len; i++){
buf[i] = toupper(buf[i]);//小写转大写API函数
}
write(cli_fd, buf, len);
//6.close
close(cli_fd);
}
//7.end
close(ser_fd);
return 0;
}
客户端代码:
//client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#define SERV_POST 8000
#define MAXLINE 80
int main(int argc, char *argv[]){
struct sockaddr_in servaddr;
int confd,n;
char buf[MAXLINE];
char *str;
if(argc < 2){
printf("input ./client str\n");
exit(1);
}
str = argv[1];
//1.socket
confd = socket(AF_INET, SOCK_STREAM, 0);
//初始化服务器IP和端口号。即封装IP数据报
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_POST);
inet_pton(AF_INET, "192.168.43.139", &servaddr.sin_addr.s_addr);
//2.connect
connect(confd, (struct sockaddr *)&servaddr, sizeof(servaddr));
//3.do
write(confd, str, strlen(str));
n = read(confd, buf, MAXLINE);
write(1,buf,n);
printf("\n");
//4.end
close(confd);
return 0;
}
Makefile:
all:server client
server:server.c
gcc $< -o $@
client:client.c
gcc $< -o $@
.Phony:clean
clean:
rm -f server
rm -f client